feat: generalize social memory and relationships
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
class_name KnownEventStateRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_VERSION := 3
|
||||
const SCHEMA_VERSION := 4
|
||||
const LEGACY_SCHEMA_VERSION := 3
|
||||
const NO_SOURCE_NPC_ID := -1
|
||||
const NO_SOURCE_ACTOR_ID := -2
|
||||
const DEFAULT_CONFIDENCE := 1.0
|
||||
const DEFAULT_SALIENCE := 0.5
|
||||
const AUTO_HOP_COUNT := -1
|
||||
|
||||
const VALID_ACQUISITION_METHODS := [
|
||||
SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED,
|
||||
@@ -28,8 +33,28 @@ static func create(
|
||||
acquisition_method: StringName = SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY,
|
||||
source_npc_id: int = NO_SOURCE_NPC_ID,
|
||||
acquired_tick: int = 0,
|
||||
source_acquisition_method: StringName = &""
|
||||
source_acquisition_method: StringName = &"",
|
||||
hop_count: int = AUTO_HOP_COUNT,
|
||||
confidence: float = DEFAULT_CONFIDENCE,
|
||||
salience: float = DEFAULT_SALIENCE,
|
||||
source_actor_id: int = NO_SOURCE_ACTOR_ID,
|
||||
pinned: bool = false
|
||||
) -> KnownEventStateRecord:
|
||||
var is_communicated := acquisition_method == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED
|
||||
var canonical_source_actor_id := source_actor_id
|
||||
var canonical_source_npc_id := source_npc_id
|
||||
if is_communicated:
|
||||
if canonical_source_actor_id == NO_SOURCE_ACTOR_ID:
|
||||
canonical_source_actor_id = source_npc_id
|
||||
canonical_source_npc_id = (
|
||||
canonical_source_actor_id if canonical_source_actor_id >= 0 else NO_SOURCE_NPC_ID
|
||||
)
|
||||
else:
|
||||
canonical_source_actor_id = NO_SOURCE_ACTOR_ID
|
||||
canonical_source_npc_id = NO_SOURCE_NPC_ID
|
||||
var canonical_hop_count := hop_count
|
||||
if canonical_hop_count == AUTO_HOP_COUNT:
|
||||
canonical_hop_count = 1 if is_communicated else 0
|
||||
return (
|
||||
KnownEventStateRecord
|
||||
. new(
|
||||
@@ -38,56 +63,82 @@ static func create(
|
||||
"knower_id": knower_id,
|
||||
"event_id": event_id,
|
||||
"acquisition_method": String(acquisition_method),
|
||||
"source_npc_id": source_npc_id,
|
||||
"source_npc_id": canonical_source_npc_id,
|
||||
"source_actor_id": canonical_source_actor_id,
|
||||
"acquired_tick": acquired_tick,
|
||||
"source_acquisition_method": String(source_acquisition_method),
|
||||
"hop_count": canonical_hop_count,
|
||||
"confidence": clampf(confidence, 0.0, 1.0),
|
||||
"salience": clampf(salience, 0.0, 1.0),
|
||||
"pinned": pinned,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
static func from_dictionary(record_data: Dictionary) -> KnownEventStateRecord:
|
||||
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
|
||||
var version := int(record_data.get("schema_version", -1))
|
||||
var normalized := record_data.duplicate(true)
|
||||
if version == LEGACY_SCHEMA_VERSION:
|
||||
normalized = _migrate_v3(record_data)
|
||||
elif version != SCHEMA_VERSION:
|
||||
return null
|
||||
if not (
|
||||
record_data
|
||||
normalized
|
||||
. has_all(
|
||||
[
|
||||
"knower_id",
|
||||
"event_id",
|
||||
"acquisition_method",
|
||||
"source_npc_id",
|
||||
"source_actor_id",
|
||||
"acquired_tick",
|
||||
"source_acquisition_method",
|
||||
"hop_count",
|
||||
"confidence",
|
||||
"salience",
|
||||
"pinned",
|
||||
]
|
||||
)
|
||||
):
|
||||
return null
|
||||
var knower_id := int(record_data["knower_id"])
|
||||
var event_id := int(record_data["event_id"])
|
||||
var acquisition_method := StringName(record_data["acquisition_method"])
|
||||
var source_npc_id := int(record_data["source_npc_id"])
|
||||
var acquired_tick := int(record_data["acquired_tick"])
|
||||
var source_acquisition_method := StringName(record_data["source_acquisition_method"])
|
||||
if knower_id < 0 or event_id < 0 or acquired_tick < 0:
|
||||
var knower_id := int(normalized["knower_id"])
|
||||
var event_id := int(normalized["event_id"])
|
||||
var acquisition_method := StringName(normalized["acquisition_method"])
|
||||
var source_npc_id := int(normalized["source_npc_id"])
|
||||
var source_actor_id := int(normalized["source_actor_id"])
|
||||
var acquired_tick := int(normalized["acquired_tick"])
|
||||
var source_acquisition_method := StringName(normalized["source_acquisition_method"])
|
||||
var hop_count := int(normalized["hop_count"])
|
||||
var confidence := float(normalized["confidence"])
|
||||
var salience := float(normalized["salience"])
|
||||
if not is_valid_actor_id(knower_id) or event_id < 0 or acquired_tick < 0 or hop_count < 0:
|
||||
return null
|
||||
if not is_finite(confidence) or confidence < 0.0 or confidence > 1.0:
|
||||
return null
|
||||
if not is_finite(salience) or salience < 0.0 or salience > 1.0:
|
||||
return null
|
||||
if typeof(normalized["pinned"]) != TYPE_BOOL:
|
||||
return null
|
||||
if acquisition_method not in VALID_ACQUISITION_METHODS:
|
||||
return null
|
||||
if acquisition_method == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED:
|
||||
if source_npc_id < 0 or source_npc_id == knower_id:
|
||||
if not is_valid_actor_id(source_actor_id) or source_actor_id == knower_id:
|
||||
return null
|
||||
if source_acquisition_method not in DIRECT_ACQUISITION_METHODS:
|
||||
if source_actor_id >= 0 and source_npc_id != source_actor_id:
|
||||
return null
|
||||
elif source_npc_id != NO_SOURCE_NPC_ID or not source_acquisition_method.is_empty():
|
||||
if source_actor_id == SimulationIds.PLAYER_ACTOR_ID and source_npc_id != NO_SOURCE_NPC_ID:
|
||||
return null
|
||||
if source_acquisition_method not in DIRECT_ACQUISITION_METHODS or hop_count != 1:
|
||||
return null
|
||||
elif (
|
||||
source_npc_id != NO_SOURCE_NPC_ID
|
||||
or source_actor_id != NO_SOURCE_ACTOR_ID
|
||||
or not source_acquisition_method.is_empty()
|
||||
or hop_count != 0
|
||||
):
|
||||
return null
|
||||
return create(
|
||||
knower_id,
|
||||
event_id,
|
||||
acquisition_method,
|
||||
source_npc_id,
|
||||
acquired_tick,
|
||||
source_acquisition_method
|
||||
)
|
||||
return KnownEventStateRecord.new(normalized)
|
||||
|
||||
|
||||
func get_knower_id() -> int:
|
||||
@@ -106,6 +157,10 @@ func get_source_npc_id() -> int:
|
||||
return int(data["source_npc_id"])
|
||||
|
||||
|
||||
func get_source_actor_id() -> int:
|
||||
return int(data["source_actor_id"])
|
||||
|
||||
|
||||
func get_acquired_tick() -> int:
|
||||
return int(data["acquired_tick"])
|
||||
|
||||
@@ -114,5 +169,69 @@ func get_source_acquisition_method() -> StringName:
|
||||
return StringName(data["source_acquisition_method"])
|
||||
|
||||
|
||||
func get_hop_count() -> int:
|
||||
return int(data["hop_count"])
|
||||
|
||||
|
||||
func get_confidence() -> float:
|
||||
return float(data["confidence"])
|
||||
|
||||
|
||||
func get_salience() -> float:
|
||||
return float(data["salience"])
|
||||
|
||||
|
||||
func is_pinned() -> bool:
|
||||
return bool(data["pinned"])
|
||||
|
||||
|
||||
func set_pinned(pinned: bool) -> bool:
|
||||
if is_pinned() == pinned:
|
||||
return false
|
||||
data["pinned"] = pinned
|
||||
return true
|
||||
|
||||
|
||||
func set_confidence(confidence: float) -> bool:
|
||||
if not is_finite(confidence):
|
||||
return false
|
||||
var bounded := clampf(confidence, 0.0, 1.0)
|
||||
if is_equal_approx(get_confidence(), bounded):
|
||||
return false
|
||||
data["confidence"] = bounded
|
||||
return true
|
||||
|
||||
|
||||
func set_salience(salience: float) -> bool:
|
||||
if not is_finite(salience):
|
||||
return false
|
||||
var bounded := clampf(salience, 0.0, 1.0)
|
||||
if is_equal_approx(get_salience(), bounded):
|
||||
return false
|
||||
data["salience"] = bounded
|
||||
return true
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
return data.duplicate(true)
|
||||
|
||||
|
||||
static func is_valid_actor_id(actor_id: int) -> bool:
|
||||
return actor_id >= 0 or actor_id == SimulationIds.PLAYER_ACTOR_ID
|
||||
|
||||
|
||||
static func _migrate_v3(record_data: Dictionary) -> Dictionary:
|
||||
var migrated := record_data.duplicate(true)
|
||||
var acquisition_method := StringName(migrated.get("acquisition_method", &""))
|
||||
var is_communicated := acquisition_method == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED
|
||||
migrated["schema_version"] = SCHEMA_VERSION
|
||||
migrated["source_actor_id"] = (
|
||||
int(migrated.get("source_npc_id", NO_SOURCE_NPC_ID))
|
||||
if is_communicated
|
||||
else NO_SOURCE_ACTOR_ID
|
||||
)
|
||||
migrated["hop_count"] = 1 if is_communicated else 0
|
||||
migrated["confidence"] = DEFAULT_CONFIDENCE
|
||||
migrated["salience"] = DEFAULT_SALIENCE
|
||||
migrated["pinned"] = false
|
||||
return migrated
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
class_name RelationshipStateRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_VERSION := 1
|
||||
const SCHEMA_VERSION := 2
|
||||
const LEGACY_SCHEMA_VERSION := 1
|
||||
const NO_CAUSE_EVENT := -1
|
||||
const NEUTRAL_TRUST := 0.5
|
||||
|
||||
const DIMENSION_FAMILIARITY := &"familiarity"
|
||||
const DIMENSION_TRUST := &"trust"
|
||||
const DIMENSION_AFFECTION := &"affection"
|
||||
const DIMENSION_RESPECT := &"respect"
|
||||
const DIMENSION_FEAR := &"fear"
|
||||
const DIMENSION_OBLIGATION := &"obligation"
|
||||
const DIMENSION_HOSTILITY := &"hostility"
|
||||
const DIMENSIONS := [
|
||||
DIMENSION_FAMILIARITY,
|
||||
DIMENSION_TRUST,
|
||||
DIMENSION_AFFECTION,
|
||||
DIMENSION_RESPECT,
|
||||
DIMENSION_FEAR,
|
||||
DIMENSION_OBLIGATION,
|
||||
DIMENSION_HOSTILITY,
|
||||
]
|
||||
|
||||
var data: Dictionary
|
||||
|
||||
|
||||
@@ -28,34 +46,50 @@ static func create(
|
||||
"subject_id": subject_id,
|
||||
"familiarity": clampf(familiarity, 0.0, 1.0),
|
||||
"trust": clampf(trust, 0.0, 1.0),
|
||||
"affection": 0.0,
|
||||
"respect": 0.0,
|
||||
"fear": 0.0,
|
||||
"obligation": 0.0,
|
||||
"hostility": 0.0,
|
||||
"last_familiarity_cause_event_id": NO_CAUSE_EVENT,
|
||||
"last_trust_cause_event_id": last_trust_cause_event_id,
|
||||
"last_affection_cause_event_id": NO_CAUSE_EVENT,
|
||||
"last_respect_cause_event_id": NO_CAUSE_EVENT,
|
||||
"last_fear_cause_event_id": NO_CAUSE_EVENT,
|
||||
"last_obligation_cause_event_id": NO_CAUSE_EVENT,
|
||||
"last_hostility_cause_event_id": NO_CAUSE_EVENT,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
static func from_dictionary(record_data: Dictionary) -> RelationshipStateRecord:
|
||||
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
|
||||
var version := int(record_data.get("schema_version", -1))
|
||||
var normalized := record_data.duplicate(true)
|
||||
if version == LEGACY_SCHEMA_VERSION:
|
||||
normalized = _migrate_v1(record_data)
|
||||
elif version != SCHEMA_VERSION:
|
||||
return null
|
||||
if not record_data.has_all(
|
||||
["observer_id", "subject_id", "familiarity", "trust", "last_trust_cause_event_id"]
|
||||
):
|
||||
var required_fields := ["observer_id", "subject_id"]
|
||||
for dimension in DIMENSIONS:
|
||||
required_fields.append(String(dimension))
|
||||
required_fields.append(_cause_key(dimension))
|
||||
if not normalized.has_all(required_fields):
|
||||
return null
|
||||
var observer_id := int(record_data["observer_id"])
|
||||
var subject_id := int(record_data["subject_id"])
|
||||
var familiarity := float(record_data["familiarity"])
|
||||
var trust := float(record_data["trust"])
|
||||
var cause_event_id := int(record_data["last_trust_cause_event_id"])
|
||||
var is_player_subject := subject_id == SimulationIds.PLAYER_ACTOR_ID
|
||||
if observer_id < 0 or observer_id == subject_id:
|
||||
var observer_id := int(normalized["observer_id"])
|
||||
var subject_id := int(normalized["subject_id"])
|
||||
if not is_valid_actor_id(observer_id) or not is_valid_actor_id(subject_id):
|
||||
return null
|
||||
if not is_player_subject and subject_id < 0:
|
||||
if observer_id == subject_id:
|
||||
return null
|
||||
if familiarity < 0.0 or familiarity > 1.0 or trust < 0.0 or trust > 1.0:
|
||||
return null
|
||||
if cause_event_id < NO_CAUSE_EVENT:
|
||||
return null
|
||||
return create(observer_id, subject_id, familiarity, trust, cause_event_id)
|
||||
for dimension in DIMENSIONS:
|
||||
var value := float(normalized[String(dimension)])
|
||||
var cause_event_id := int(normalized[_cause_key(dimension)])
|
||||
if not is_finite(value) or value < 0.0 or value > 1.0:
|
||||
return null
|
||||
if cause_event_id < NO_CAUSE_EVENT:
|
||||
return null
|
||||
return RelationshipStateRecord.new(normalized)
|
||||
|
||||
|
||||
func get_observer_id() -> int:
|
||||
@@ -67,31 +101,162 @@ func get_subject_id() -> int:
|
||||
|
||||
|
||||
func get_familiarity() -> float:
|
||||
return float(data["familiarity"])
|
||||
return get_dimension(DIMENSION_FAMILIARITY)
|
||||
|
||||
|
||||
func get_trust() -> float:
|
||||
return float(data["trust"])
|
||||
return get_dimension(DIMENSION_TRUST)
|
||||
|
||||
|
||||
func get_affection() -> float:
|
||||
return get_dimension(DIMENSION_AFFECTION)
|
||||
|
||||
|
||||
func get_respect() -> float:
|
||||
return get_dimension(DIMENSION_RESPECT)
|
||||
|
||||
|
||||
func get_fear() -> float:
|
||||
return get_dimension(DIMENSION_FEAR)
|
||||
|
||||
|
||||
func get_obligation() -> float:
|
||||
return get_dimension(DIMENSION_OBLIGATION)
|
||||
|
||||
|
||||
func get_hostility() -> float:
|
||||
return get_dimension(DIMENSION_HOSTILITY)
|
||||
|
||||
|
||||
func get_dimension(dimension: StringName) -> float:
|
||||
if not has_dimension(dimension):
|
||||
return -1.0
|
||||
return float(data[String(dimension)])
|
||||
|
||||
|
||||
func get_last_familiarity_cause_event_id() -> int:
|
||||
return get_last_cause_event_id(DIMENSION_FAMILIARITY)
|
||||
|
||||
|
||||
func get_last_trust_cause_event_id() -> int:
|
||||
return int(data["last_trust_cause_event_id"])
|
||||
return get_last_cause_event_id(DIMENSION_TRUST)
|
||||
|
||||
|
||||
func get_last_affection_cause_event_id() -> int:
|
||||
return get_last_cause_event_id(DIMENSION_AFFECTION)
|
||||
|
||||
|
||||
func get_last_respect_cause_event_id() -> int:
|
||||
return get_last_cause_event_id(DIMENSION_RESPECT)
|
||||
|
||||
|
||||
func get_last_fear_cause_event_id() -> int:
|
||||
return get_last_cause_event_id(DIMENSION_FEAR)
|
||||
|
||||
|
||||
func get_last_obligation_cause_event_id() -> int:
|
||||
return get_last_cause_event_id(DIMENSION_OBLIGATION)
|
||||
|
||||
|
||||
func get_last_hostility_cause_event_id() -> int:
|
||||
return get_last_cause_event_id(DIMENSION_HOSTILITY)
|
||||
|
||||
|
||||
func get_last_cause_event_id(dimension: StringName) -> int:
|
||||
if not has_dimension(dimension):
|
||||
return NO_CAUSE_EVENT
|
||||
return int(data[_cause_key(dimension)])
|
||||
|
||||
|
||||
func increase_familiarity(amount: float) -> float:
|
||||
var previous := get_familiarity()
|
||||
data["familiarity"] = clampf(previous + maxf(amount, 0.0), 0.0, 1.0)
|
||||
return get_familiarity() - previous
|
||||
return adjust_dimension(DIMENSION_FAMILIARITY, maxf(amount, 0.0), NO_CAUSE_EVENT)
|
||||
|
||||
|
||||
func increase_familiarity_with_cause(amount: float, cause_event_id: int) -> float:
|
||||
return adjust_dimension(DIMENSION_FAMILIARITY, maxf(amount, 0.0), cause_event_id)
|
||||
|
||||
|
||||
func increase_trust(amount: float, cause_event_id: int) -> float:
|
||||
var previous := get_trust()
|
||||
data["trust"] = clampf(previous + maxf(amount, 0.0), 0.0, 1.0)
|
||||
var applied := get_trust() - previous
|
||||
if applied > 0.0:
|
||||
data["last_trust_cause_event_id"] = cause_event_id
|
||||
return adjust_dimension(DIMENSION_TRUST, maxf(amount, 0.0), cause_event_id)
|
||||
|
||||
|
||||
func adjust_affection(amount: float, cause_event_id: int) -> float:
|
||||
return adjust_dimension(DIMENSION_AFFECTION, amount, cause_event_id)
|
||||
|
||||
|
||||
func adjust_respect(amount: float, cause_event_id: int) -> float:
|
||||
return adjust_dimension(DIMENSION_RESPECT, amount, cause_event_id)
|
||||
|
||||
|
||||
func adjust_fear(amount: float, cause_event_id: int) -> float:
|
||||
return adjust_dimension(DIMENSION_FEAR, amount, cause_event_id)
|
||||
|
||||
|
||||
func adjust_obligation(amount: float, cause_event_id: int) -> float:
|
||||
return adjust_dimension(DIMENSION_OBLIGATION, amount, cause_event_id)
|
||||
|
||||
|
||||
func adjust_hostility(amount: float, cause_event_id: int) -> float:
|
||||
return adjust_dimension(DIMENSION_HOSTILITY, amount, cause_event_id)
|
||||
|
||||
|
||||
func adjust_dimension(dimension: StringName, amount: float, cause_event_id: int) -> float:
|
||||
if not has_dimension(dimension) or not is_finite(amount) or cause_event_id < NO_CAUSE_EVENT:
|
||||
return 0.0
|
||||
var previous := get_dimension(dimension)
|
||||
var current := clampf(previous + amount, 0.0, 1.0)
|
||||
var applied := current - previous
|
||||
if is_zero_approx(applied):
|
||||
return 0.0
|
||||
data[String(dimension)] = current
|
||||
if cause_event_id != NO_CAUSE_EVENT:
|
||||
data[_cause_key(dimension)] = cause_event_id
|
||||
return applied
|
||||
|
||||
|
||||
func get_salience() -> float:
|
||||
var salience := get_familiarity()
|
||||
salience = maxf(salience, absf(get_trust() - NEUTRAL_TRUST) * 2.0)
|
||||
for dimension in [
|
||||
DIMENSION_AFFECTION,
|
||||
DIMENSION_RESPECT,
|
||||
DIMENSION_FEAR,
|
||||
DIMENSION_OBLIGATION,
|
||||
DIMENSION_HOSTILITY,
|
||||
]:
|
||||
salience = maxf(salience, get_dimension(dimension))
|
||||
return clampf(salience, 0.0, 1.0)
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
return data.duplicate(true)
|
||||
|
||||
|
||||
static func has_dimension(dimension: StringName) -> bool:
|
||||
return dimension in DIMENSIONS
|
||||
|
||||
|
||||
static func is_valid_actor_id(actor_id: int) -> bool:
|
||||
return actor_id >= 0 or actor_id == SimulationIds.PLAYER_ACTOR_ID
|
||||
|
||||
|
||||
static func _cause_key(dimension: StringName) -> String:
|
||||
return "last_%s_cause_event_id" % String(dimension)
|
||||
|
||||
|
||||
static func _migrate_v1(record_data: Dictionary) -> Dictionary:
|
||||
var migrated := record_data.duplicate(true)
|
||||
migrated["schema_version"] = SCHEMA_VERSION
|
||||
for dimension in [
|
||||
DIMENSION_AFFECTION,
|
||||
DIMENSION_RESPECT,
|
||||
DIMENSION_FEAR,
|
||||
DIMENSION_OBLIGATION,
|
||||
DIMENSION_HOSTILITY,
|
||||
]:
|
||||
migrated[String(dimension)] = 0.0
|
||||
for dimension in DIMENSIONS:
|
||||
var cause_key := _cause_key(dimension)
|
||||
if not migrated.has(cause_key):
|
||||
migrated[cause_key] = NO_CAUSE_EVENT
|
||||
return migrated
|
||||
|
||||
Reference in New Issue
Block a user