Dice: Backport 0.2.0 face_values format (kinda) (#308)

* Dice: Multiple normals per face value

* face_values values can now be Vector2 *or* an array of Vector2. Internal representation is array of Vector3. Array mustn't be empty so that code which sets the value works and makes sense.
* Removed restriction on value count matching dice sides. (While the total amount of normals could be checked, IMO this restriction is arbitrary and only likely to cause more confusion than it solves. Branch is on the way out anyway so any change that would really break this will probably nuke everything anyway.)

Tested on a personal project; a screenshot will be included with the PR.

* Dice: face_values: Probably should sanity-check that there's at least one normal in the AssetDB, as it could error otherwise if the user tries to set a value

* Dice: face_values: Implement 0.2.0-style

* Dice: Limit value data types to what v0.2.0 will accept, fix documentation
This commit is contained in:
20kdc 2023-09-17 14:19:31 +01:00 committed by GitHub
parent 480db0ffda
commit 10f637affd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 60 deletions

View File

@ -112,8 +112,8 @@ orientation in the :ref:`config-cfg` file, using the ``face_values`` property:
face_values = {
; This is the format of an entry in face_values:
; A number, followed by a semi-colon, followed by a Vector2, with two
; numbers inside, followed by a comma if it is not the last entry.
; A number, followed by a semi-colon, followed by a Vector2. Each Vector2
; has two numbers inside, followed by a comma if it is not the last entry.
1: Vector2(0.0, 0.0),
; The two numbers inside the Vector2 correspond to the rotation in the
@ -126,7 +126,12 @@ orientation in the :ref:`config-cfg` file, using the ``face_values`` property:
3: Vector2(-90.0, 0.0),
4: Vector2(90.0, 0.0),
5: Vector2(0.0, -90.0),
6: Vector2(180.0, 0.0)
; Alternatively, the Vector2 and the number may swap places.
; This allows multiple rotations with the same value.
; Values given this way may be null, numbers, or strings.
; Values that can't be parsed as numbers are totalled as 0.
Vector2(180.0, 0.0): "6"
}
If the face values are configured correctly, then the player will easily be able
@ -136,7 +141,6 @@ dice. The total will be shown at the top of the context menu.
If ``face_values`` is not configured, the dice will always report ``0`` as its
value.
.. _object-type-piece:
Piece

View File

@ -1252,27 +1252,46 @@ func _import_asset(from: String, pack: String, type: String, config: ConfigFile,
var face_values: Dictionary = _get_file_config_value(config,
from.get_file(), "face_values", {})
var face_values_entry = {}
# Maps values (strings) to Arrays of Vector3 (normals).
# Arrays will never be empty.
var face_values_entry := {}
# Total normals to check against faces
var num_normals := 0
if not face_values.empty():
if face_values.size() == num_faces:
for key in face_values:
for key in face_values:
var value = face_values[key]
# Figure out 0.2.0 A:V versus 0.1.x V:A
var fv_angle: Vector2
var fv_value: String
if key is Vector2:
# key is known so don't check it
if not ((value == null) or value is int or value is float or value is String):
# this is more about 0.2.0 compat (the data is stringified anyway)
_log_error("New-style value in face_values entry is not a number, string, or null! (%s)" % str(key))
return ERR_INVALID_DATA
fv_angle = key
fv_value = str(value)
else:
if not (key is int or key is float):
_log_error("Key in face_values entry is not a number! (%s)" % str(key))
_log_error("Old-style key in face_values entry is not a number! (%s)" % str(key))
return ERR_INVALID_DATA
var value = face_values[key]
if not value is Vector2:
_log_error("Value in face_values entry is not a Vector2! (%s)" % str(value))
_log_error("Old-style value in face_values entry is not a Vector2! (%s)" % str(value))
return ERR_INVALID_DATA
var normal_vec = _precalculate_face_value_normal(value)
face_values_entry[key] = normal_vec
else:
_log_error("Number of entries for face_values (%d) does not match the number of faces (%d)!" %
[face_values.size(), num_faces])
return ERR_INVALID_DATA
fv_value = str(key)
fv_angle = value
if not fv_value in face_values_entry:
face_values_entry[fv_value] = []
face_values_entry[fv_value].append(_precalculate_face_value_normal(fv_angle))
num_normals += 1
entry["face_values"] = face_values_entry
if num_normals != num_faces:
_log_warning("face_values describes less normals (%d) than the dice should have faces (%d)" % [num_normals, num_faces])
if type == "cards" or type.begins_with("tokens"):
# If we use null as a default value, ConfigFile will throw an error
@ -1662,37 +1681,28 @@ func _is_valid_entry(pack: String, type: String, entry: Dictionary) -> bool:
_log_error("'face_values' in entry is not a dictionary!")
return false
var num_faces = 0
if type.ends_with("d4"):
num_faces = 4
elif type.ends_with("d6"):
num_faces = 6
elif type.ends_with("d8"):
num_faces = 8
elif type.ends_with("d10"):
num_faces = 10
elif type.ends_with("d12"):
num_faces = 12
elif type.ends_with("d20"):
num_faces = 20
if value.size() != num_faces:
_log_error("'face_values' dictionary in entry is not the expected size (%d)!" % num_faces)
return false
for element_key in value:
if not (typeof(element_key) == TYPE_INT or typeof(element_key) == TYPE_REAL):
_log_error("'face_values' key in entry is not a number!")
if typeof(element_key) != TYPE_STRING:
_log_error("'face_values' key in entry is not a string!")
return false
var element_value = value[element_key]
if typeof(element_value) != TYPE_VECTOR3:
_log_error("'face_values' value in entry is not a Vector3!")
if typeof(element_value) != TYPE_ARRAY:
_log_error("'face_values' value in entry is not an Array!")
return false
if not is_equal_approx(element_value.length_squared(), 1.0):
_log_error("'face_values' vector in entry is not unit length!")
if len(element_value) == 0:
_log_error("'face_values' value in entry is empty!")
return false
for normal in element_value:
if typeof(normal) != TYPE_VECTOR3:
_log_error("'face_values' sub-value in entry is not a Vector3!")
return false
if not is_equal_approx(normal.length_squared(), 1.0):
_log_error("'face_values' vector in entry is not unit length!")
return false
"hands":
if typeof(value) != TYPE_ARRAY:
_log_error("'hands' in entry is not an array!")

View File

@ -1256,10 +1256,14 @@ func _popup_piece_context_menu() -> void:
prev_num_items = _piece_context_menu.get_item_count()
if _inheritance_has(inheritance, "Dice"):
var total = 0.0
var available_values = []
var single_dice_value := ""
var total := 0.0
var available_values := []
for dice in _selected_pieces:
total += dice.get_face_value()
var value: String = dice.get_face_value()
single_dice_value = value
if value.is_valid_float():
total += float(value)
var face_value_dict: Dictionary = dice.piece_entry["face_values"]
for possible_value in face_value_dict.keys():
@ -1267,13 +1271,21 @@ func _popup_piece_context_menu() -> void:
available_values.append(possible_value)
available_values.sort()
var is_int = (round(total) == total)
if is_int:
_piece_context_menu.add_item(tr("Total: %d") % total)
if len(_selected_pieces) == 1:
# If there's a single piece, we should show value text (if any).
# (There are various symbol dice that benefit, i.e. Zombie Dice)
if single_dice_value == "":
_piece_context_menu.add_item(tr("Value: (None)"))
else:
_piece_context_menu.add_item(tr("Value: %s") % single_dice_value)
else:
_piece_context_menu.add_item(tr("Total: %f") % total)
var is_int = (round(total) == total)
if is_int:
_piece_context_menu.add_item(tr("Total: %d") % total)
else:
_piece_context_menu.add_item(tr("Total: %f") % total)
var prev_value = 1
var prev_value := single_dice_value
if _dice_value_button.selected >= 0:
prev_value = _dice_value_button.get_item_metadata(
_dice_value_button.selected)
@ -2535,7 +2547,7 @@ func _on_SetDiceValueButton_pressed():
if not face_value_dict.has(value_to_set):
continue
var face_value_normal: Vector3 = face_value_dict[value_to_set]
var face_value_normal: Vector3 = face_value_dict[value_to_set][0]
var rotation_quat: Quat
if face_value_normal.is_equal_approx(Vector3.UP):

View File

@ -31,20 +31,21 @@ export(Resource) var shake_sounds
var _rng = RandomNumberGenerator.new()
# Get the value of the face that is currently pointed upwards.
# Returns: The top face's value, 0 if no values are configured.
func get_face_value() -> float:
# Returns: The top face's value, the empty string if no values are configured.
func get_face_value() -> String:
var face_values: Dictionary = piece_entry["face_values"]
if face_values.empty():
return 0.0
return ""
var max_dot = -1.0
var closest_value = 0
var closest_value: String = "0"
for value in face_values:
var normal = face_values[value]
var dot = transform.basis.xform(normal).dot(Vector3.UP)
if dot > max_dot:
max_dot = dot
closest_value = value
var normals: Array = face_values[value]
for normal in normals:
var dot = transform.basis.xform(normal).dot(Vector3.UP)
if dot > max_dot:
max_dot = dot
closest_value = value
return closest_value