For those interested in reading face attributes for random characters, here's the extended function/polyfill for
Here is a test function with a funny code that shows how to use it, check out the result here:
Next step would be to have another function that lets you set those face attributes at runtime, too. That should also be possible for morph keys 0-41 and let you build your own Pinocchio depending on how the current quest is going, or dialog responses.
The code is pretty short, I just added a lot of comments and explanations, because how it works is kind of backwards. If you want more information about how these funky face keys work, they are documented them extensively here, scroll down.
face_keys_get_morph_key
. For the first time ever you'll be able to insult the player by measuring how big their nose or ears are at runtime, or whatever:
Python:
# face_keys_get_extended_morph_key
# Stores face key's morph key value (0-20) into reg0; by exploiting some sneaky
# string trickery we can read outside of the limited 0-7 range TaleWorlds gave us. >:)
#
# This is a replacement or 'polyfill' for the limited (face_keys_get_morph_key) operation.
# You can use this handy online tool to inspect and generate these face codes:
# https://swyter.github.io/mab-tools/face
#
# Note: Keep in mind that the actual range of available keys goes from 0 to 41.
# But in this implementation we can only access until 'Eyebrow Height' in Native.
# So if you want to read 'Eyebrow Depth' onwards you are out of luck.
# Still, 0-20 is still much better than the ridiculous 0-7 range.
#
# Input: param1: string_index containing the 64-hex-character face code, usually as returned by (str_store_troop_face_keys) or (str_store_player_face_keys), doesn't matter if they are in upper or lowercase, as long as they only consist in numbers from 0-9 and letters from a-f. face_keys_get_morph_key only cares about the 64 leftmost characters and discards the rest, that's why this trick works.
# Input: param2: key_no, the morph key index you are interested in; from 0 to 20, both included.
# Output: reg0: selected face key's value
("face_keys_get_extended_morph_key",
[
(store_script_param_1, ":string_index"),
(store_script_param_2, ":key_no"),
# |
# V first three bits for morph key 0
# ----
# ---- 111 <- fk00
# | ---- 11 1 <- fk01
# bit pattern repeats at key 4 V 1 11 <- fk02
# ---- 111 <- fk03
# ---- 111 <- fk04 (three bits at start again, like fk00)
# ---- 11 1 <- fk05 (three bits like fk01)
# 1 11 ...
# 111
# (There are four 3-bit morph keys in each 3 4-bit nibbles/hex characters, 3*4=12 bits)
#
# https://swyter.github.io/mab-tools/face#0x000000000000000070070070070070070000000000000000000000000000000a
#
#
# 0, 4, 8, 12, 16, 20 (same bit pos as face key 0)
# 1, 5, 9, 13, 17 (same bit pos as face key 1)
# 2, 6, 10, 14, 18 (same bit pos as face key 2)
# 3, 7, 11, 15, 19 (same bit pos as face key 3)
#
#
# 0x00000000000000007007007007007007 0/4=0*3 <- how many 4-bit hex characters we need to prepend to move the string to get it at the face key 0 position
# 0x00000000000000007007007007007 +3 = 3 4/4=1*3
# 0x00000000000000007007007007 +3 = 6 8/4=2*3
# 0x00000000000000007007007 +3 = 9 12/4=3*3
# 0x00000000000000007007 +3 = 12 16/4=4*3
# 0x00000000000000007 +3 = 15 20/4=5*3
#
# if index > 20:
# exit
#
# pad = (index/4)*3
#
# while pad--:
# 'A' + key
#
# if (index % 4) == 0: face_keys_get_morph_key(0, 0)
# if (index % 4) == 1: face_keys_get_morph_key(0, 1)
# if (index % 4) == 2: face_keys_get_morph_key(0, 2)
# if (index % 4) == 3: face_keys_get_morph_key(0, 3)
(try_begin),
(this_or_next|lt, ":key_no", 0),
( gt, ":key_no", 20),
# swy: due to limitations of this string-prepending method we only support keys
# that are left-ward from the base fk0 position. from 21 onwards they are
# in the right (c) part, and we can't seemingly shorten strings for now,
# only prepend extra letters.
# <--|
# 00000000000000006b1a20a72efac68814e5df58d1053977000000000000000a
# [ a ]^^^^^^^^^^^^^^^^[ c ][ d ]
# 00000000000000006b1a20a72efac6880000000000000000000000000000000a
(assign, reg0, -1),
(else_try),
# swy: we were asked for a key between 0 and 20
(set_fixed_point_multiplier, 1), # swy: we want integer division without decimals for this to work
(store_div, ":pad", ":key_no", 4),
( val_mul, ":pad", 3), # swy: pad = (key_no / 4) * 3
(str_store_string_reg, s2, ":string_index"),
(try_for_range, ":dummy", 0, ":pad"),
(str_store_string, s2, "@F{s2}"), # swy: move the useful part of the string to the right, one hex character at a time
(end_try),
(store_mod, ":key_no_mod_four", ":key_no", 4),
(try_begin), (eq,":key_no_mod_four", 0), (face_keys_get_morph_key, reg0, s2, 0), # swy: 0, 4, 8, 12, 16, 20 (same bit pos as face key 0)
( else_try), (eq,":key_no_mod_four", 1), (face_keys_get_morph_key, reg0, s2, 1), # swy: 1, 5, 9, 13, 17 (same bit pos as face key 1)
( else_try), (eq,":key_no_mod_four", 2), (face_keys_get_morph_key, reg0, s2, 2), # swy: 2, 6, 10, 14, 18 (same bit pos as face key 2)
( else_try), (eq,":key_no_mod_four", 3), (face_keys_get_morph_key, reg0, s2, 3), # swy: 3, 7, 11, 15, 19 (same bit pos as face key 3)
( try_end),
(end_try)
]),
Here is a test function with a funny code that shows how to use it, check out the result here:
Python:
(try_for_range, reg99, 0, 22 + 1),
#(str_store_string, s70, "@00000000000000007db6b6d9244922490000000000000000000000000000000a"),
(str_store_string, s70, "@00000000000000006b1a20a72efac68814e5df58d1053977000000000000000a"),
(call_script, "script_face_keys_get_extended_morph_key", s70, reg99),
(display_message, "@SCRIPT RESULT: {reg99} {reg0}", 0x289128),
(try_end),
Next step would be to have another function that lets you set those face attributes at runtime, too. That should also be possible for morph keys 0-41 and let you build your own Pinocchio depending on how the current quest is going, or dialog responses.
The code is pretty short, I just added a lot of comments and explanations, because how it works is kind of backwards. If you want more information about how these funky face keys work, they are documented them extensively here, scroll down.
Last edited: