feat: add knowledge-gated pantry opportunities
This commit is contained in:
@@ -2,13 +2,14 @@ class_name SimulationStateRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_NAME := "the_steward.simulation"
|
||||
const SCHEMA_VERSION := 7
|
||||
const SCHEMA_VERSION := 8
|
||||
const LEGACY_SCHEMA_VERSION := 1
|
||||
const EVENT_LEGACY_SCHEMA_VERSION := 2
|
||||
const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3
|
||||
const KNOWLEDGE_LEGACY_SCHEMA_VERSION := 4
|
||||
const PROVENANCE_LEGACY_SCHEMA_VERSION := 5
|
||||
const PREVIOUS_SCHEMA_VERSION := 6
|
||||
const RETENTION_LEGACY_SCHEMA_VERSION := 6
|
||||
const PREVIOUS_SCHEMA_VERSION := 7
|
||||
|
||||
var simulation: Dictionary
|
||||
var village: VillageStateRecord
|
||||
@@ -18,6 +19,7 @@ var storages: Array[StorageStateRecord] = []
|
||||
var economic_events: Array[EconomicEventRecord] = []
|
||||
var relationships: Array[RelationshipStateRecord] = []
|
||||
var event_knowledge: Array[KnownEventStateRecord] = []
|
||||
var opportunities: Array[OpportunityStateRecord] = []
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
@@ -40,6 +42,9 @@ func to_dictionary() -> Dictionary:
|
||||
var knowledge_data: Array[Dictionary] = []
|
||||
for known_event_record in event_knowledge:
|
||||
knowledge_data.append(known_event_record.to_dictionary())
|
||||
var opportunity_data: Array[Dictionary] = []
|
||||
for opportunity_record in opportunities:
|
||||
opportunity_data.append(opportunity_record.to_dictionary())
|
||||
|
||||
return {
|
||||
"schema": SCHEMA_NAME,
|
||||
@@ -51,7 +56,8 @@ func to_dictionary() -> Dictionary:
|
||||
"storages": storage_data,
|
||||
"economic_events": event_data,
|
||||
"relationships": relationship_data,
|
||||
"event_knowledge": knowledge_data
|
||||
"event_knowledge": knowledge_data,
|
||||
"opportunities": opportunity_data
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +84,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
RELATIONSHIP_LEGACY_SCHEMA_VERSION,
|
||||
KNOWLEDGE_LEGACY_SCHEMA_VERSION,
|
||||
PROVENANCE_LEGACY_SCHEMA_VERSION,
|
||||
RETENTION_LEGACY_SCHEMA_VERSION,
|
||||
PREVIOUS_SCHEMA_VERSION,
|
||||
]
|
||||
):
|
||||
@@ -96,6 +103,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
"economic_events",
|
||||
"relationships",
|
||||
"event_knowledge",
|
||||
"opportunities",
|
||||
]
|
||||
)
|
||||
):
|
||||
@@ -104,22 +112,27 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
var simulation_data = record_data["simulation"]
|
||||
if not simulation_data is Dictionary:
|
||||
return null
|
||||
if not simulation_data.has_all(
|
||||
[
|
||||
"seed",
|
||||
"tick_interval",
|
||||
"tick_count",
|
||||
"clock_accumulator",
|
||||
"clock_elapsed_ticks",
|
||||
"wander_random_streams",
|
||||
"next_event_id"
|
||||
]
|
||||
if not (
|
||||
simulation_data
|
||||
. has_all(
|
||||
[
|
||||
"seed",
|
||||
"tick_interval",
|
||||
"tick_count",
|
||||
"clock_accumulator",
|
||||
"clock_elapsed_ticks",
|
||||
"wander_random_streams",
|
||||
"next_event_id",
|
||||
"next_opportunity_id",
|
||||
]
|
||||
)
|
||||
):
|
||||
return null
|
||||
var saved_tick_interval := float(simulation_data["tick_interval"])
|
||||
var saved_tick_count := int(simulation_data["tick_count"])
|
||||
var saved_clock_accumulator := float(simulation_data["clock_accumulator"])
|
||||
var saved_clock_elapsed_ticks := int(simulation_data["clock_elapsed_ticks"])
|
||||
var saved_next_opportunity_id := int(simulation_data["next_opportunity_id"])
|
||||
if (
|
||||
not is_finite(saved_tick_interval)
|
||||
or saved_tick_interval <= 0.0
|
||||
@@ -127,6 +140,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
or not is_finite(saved_clock_accumulator)
|
||||
or saved_clock_accumulator < 0.0
|
||||
or saved_clock_elapsed_ticks < 0
|
||||
or saved_next_opportunity_id < 0
|
||||
):
|
||||
return null
|
||||
if simulation_data.has("cycle_duration_seconds"):
|
||||
@@ -155,6 +169,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
var event_data = record_data["economic_events"]
|
||||
var relationship_data = record_data["relationships"]
|
||||
var knowledge_data = record_data["event_knowledge"]
|
||||
var opportunity_data = record_data["opportunities"]
|
||||
if (
|
||||
not npc_data is Array
|
||||
or not resource_data is Array
|
||||
@@ -162,6 +177,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
or not event_data is Array
|
||||
or not relationship_data is Array
|
||||
or not knowledge_data is Array
|
||||
or not opportunity_data is Array
|
||||
):
|
||||
return null
|
||||
|
||||
@@ -183,6 +199,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
record.npcs.append(npc_record)
|
||||
|
||||
var resource_ids := {}
|
||||
var resource_records_by_id := {}
|
||||
for item in resource_data:
|
||||
if not item is Dictionary:
|
||||
return null
|
||||
@@ -193,9 +210,11 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
if resource_ids.has(resource_id):
|
||||
return null
|
||||
resource_ids[resource_id] = true
|
||||
resource_records_by_id[resource_id] = resource_record
|
||||
record.resources.append(resource_record)
|
||||
|
||||
var storage_ids := {}
|
||||
var storage_records_by_id := {}
|
||||
for item in storage_data:
|
||||
if not item is Dictionary:
|
||||
return null
|
||||
@@ -206,6 +225,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
if storage_ids.has(storage_id):
|
||||
return null
|
||||
storage_ids[storage_id] = true
|
||||
storage_records_by_id[storage_id] = storage_record
|
||||
record.storages.append(storage_record)
|
||||
|
||||
var event_ids := {}
|
||||
@@ -228,6 +248,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
return null
|
||||
|
||||
var knowledge_keys := {}
|
||||
var knowledge_records_by_key := {}
|
||||
for item in knowledge_data:
|
||||
if not item is Dictionary:
|
||||
return null
|
||||
@@ -242,6 +263,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
if knowledge_keys.has(knowledge_key):
|
||||
return null
|
||||
knowledge_keys[knowledge_key] = true
|
||||
knowledge_records_by_key[knowledge_key] = known_event_record
|
||||
record.event_knowledge.append(known_event_record)
|
||||
for known_event_record in record.event_knowledge:
|
||||
if not _is_valid_knowledge_provenance(
|
||||
@@ -289,6 +311,46 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
relationship_keys[relationship_key] = true
|
||||
record.relationships.append(relationship_record)
|
||||
|
||||
var opportunity_ids := {}
|
||||
var trigger_event_ids := {}
|
||||
var resolution_event_ids := {}
|
||||
var has_open_opportunity := false
|
||||
var highest_opportunity_id := -1
|
||||
for item in opportunity_data:
|
||||
if not item is Dictionary:
|
||||
return null
|
||||
var opportunity_record := OpportunityStateRecord.from_dictionary(item)
|
||||
if opportunity_record == null:
|
||||
return null
|
||||
var opportunity_id := opportunity_record.get_opportunity_id()
|
||||
var trigger_event_id := opportunity_record.get_trigger_event_id()
|
||||
if opportunity_ids.has(opportunity_id) or trigger_event_ids.has(trigger_event_id):
|
||||
return null
|
||||
if opportunity_record.get_status() == OpportunityStateRecord.STATUS_OPEN:
|
||||
if has_open_opportunity:
|
||||
return null
|
||||
has_open_opportunity = true
|
||||
elif opportunity_record.get_resolution_event_id() != OpportunityStateRecord.NO_EVENT_ID:
|
||||
if resolution_event_ids.has(opportunity_record.get_resolution_event_id()):
|
||||
return null
|
||||
resolution_event_ids[opportunity_record.get_resolution_event_id()] = true
|
||||
if not _is_valid_opportunity(
|
||||
opportunity_record,
|
||||
npc_ids,
|
||||
storage_records_by_id,
|
||||
event_records_by_id,
|
||||
knowledge_records_by_key,
|
||||
resource_records_by_id,
|
||||
int(record.simulation["tick_count"])
|
||||
):
|
||||
return null
|
||||
opportunity_ids[opportunity_id] = true
|
||||
trigger_event_ids[trigger_event_id] = true
|
||||
highest_opportunity_id = maxi(highest_opportunity_id, opportunity_id)
|
||||
record.opportunities.append(opportunity_record)
|
||||
if int(record.simulation["next_opportunity_id"]) <= highest_opportunity_id:
|
||||
return null
|
||||
|
||||
return record
|
||||
|
||||
|
||||
@@ -310,11 +372,7 @@ static func _is_valid_knowledge_provenance(
|
||||
return false
|
||||
if method == SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY:
|
||||
return true
|
||||
if (
|
||||
StringName(event.data["event_type"]) != SimulationIds.EVENT_STORAGE_DEPOSITED
|
||||
or StringName(event.data["item_id"]) != SimulationIds.RESOURCE_FOOD
|
||||
or float(event.data["amount"]) <= 0.0
|
||||
):
|
||||
if not EventKnowledgeSystem.is_knowable_event(event):
|
||||
return false
|
||||
match method:
|
||||
SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED:
|
||||
@@ -337,6 +395,117 @@ static func _is_valid_knowledge_provenance(
|
||||
return false
|
||||
|
||||
|
||||
static func _is_valid_opportunity(
|
||||
opportunity: OpportunityStateRecord,
|
||||
npc_ids: Dictionary,
|
||||
storage_records_by_id: Dictionary,
|
||||
event_records_by_id: Dictionary,
|
||||
knowledge_records_by_key: Dictionary,
|
||||
resource_records_by_id: Dictionary,
|
||||
current_tick: int
|
||||
) -> bool:
|
||||
if opportunity.get_opportunity_type() != SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY:
|
||||
return false
|
||||
if (
|
||||
not npc_ids.has(opportunity.get_interested_npc_id())
|
||||
or opportunity.get_target_id() != SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
or not storage_records_by_id.has(opportunity.get_target_id())
|
||||
or opportunity.get_resource_id() != SimulationIds.RESOURCE_FOOD
|
||||
or not is_finite(opportunity.get_target_amount())
|
||||
or opportunity.get_target_amount() <= 0.0
|
||||
):
|
||||
return false
|
||||
var trigger_event := (
|
||||
event_records_by_id.get(opportunity.get_trigger_event_id()) as EconomicEventRecord
|
||||
)
|
||||
if trigger_event == null or not _is_valid_pantry_empty_trigger(trigger_event, npc_ids):
|
||||
return false
|
||||
var trigger_tick := int(trigger_event.data["tick"])
|
||||
var created_tick := opportunity.get_created_tick()
|
||||
if created_tick < trigger_tick or created_tick > current_tick:
|
||||
return false
|
||||
|
||||
var status := opportunity.get_status()
|
||||
var resolution_event_id := opportunity.get_resolution_event_id()
|
||||
var resolved_tick := opportunity.get_resolved_tick()
|
||||
if status == OpportunityStateRecord.STATUS_OPEN:
|
||||
var target_storage := (
|
||||
storage_records_by_id.get(opportunity.get_target_id()) as StorageStateRecord
|
||||
)
|
||||
var evidence_key := (
|
||||
"%d:%d" % [opportunity.get_interested_npc_id(), opportunity.get_trigger_event_id()]
|
||||
)
|
||||
var evidence := knowledge_records_by_key.get(evidence_key) as KnownEventStateRecord
|
||||
return (
|
||||
resolution_event_id == OpportunityStateRecord.NO_EVENT_ID
|
||||
and resolved_tick == -1
|
||||
and target_storage != null
|
||||
and (
|
||||
target_storage.get_amount(opportunity.get_resource_id())
|
||||
< opportunity.get_target_amount()
|
||||
)
|
||||
and evidence != null
|
||||
and evidence.get_acquired_tick() <= created_tick
|
||||
)
|
||||
if status != OpportunityStateRecord.STATUS_RESOLVED:
|
||||
return false
|
||||
if resolution_event_id == OpportunityStateRecord.NO_EVENT_ID:
|
||||
return false
|
||||
var resolution_event := event_records_by_id.get(resolution_event_id) as EconomicEventRecord
|
||||
if (
|
||||
resolution_event == null
|
||||
or not _is_valid_pantry_supply_resolution(resolution_event, npc_ids, resource_records_by_id)
|
||||
):
|
||||
return false
|
||||
var resolution_tick := int(resolution_event.data["tick"])
|
||||
return (
|
||||
resolution_event_id > opportunity.get_trigger_event_id()
|
||||
and resolution_tick >= created_tick
|
||||
and resolved_tick == resolution_tick
|
||||
and resolved_tick <= current_tick
|
||||
)
|
||||
|
||||
|
||||
static func _is_valid_pantry_empty_trigger(event: EconomicEventRecord, npc_ids: Dictionary) -> bool:
|
||||
var actor_id := int(event.data["actor_id"])
|
||||
return (
|
||||
npc_ids.has(actor_id)
|
||||
and StringName(event.data["event_type"]) == SimulationIds.EVENT_STORAGE_WITHDRAWN
|
||||
and StringName(event.data["source_id"]) == SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
and StringName(event.data["destination_id"]) == SimulationIds.npc_inventory_id(actor_id)
|
||||
and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
|
||||
and float(event.data["amount"]) > 0.0
|
||||
)
|
||||
|
||||
|
||||
static func _is_valid_pantry_supply_resolution(
|
||||
event: EconomicEventRecord, npc_ids: Dictionary, resource_records_by_id: Dictionary
|
||||
) -> bool:
|
||||
var actor_id := int(event.data["actor_id"])
|
||||
var event_type := StringName(event.data["event_type"])
|
||||
if (
|
||||
StringName(event.data["destination_id"]) != SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
or StringName(event.data["item_id"]) != SimulationIds.RESOURCE_FOOD
|
||||
or float(event.data["amount"]) <= 0.0
|
||||
):
|
||||
return false
|
||||
if event_type == SimulationIds.EVENT_STORAGE_DEPOSITED:
|
||||
return (
|
||||
npc_ids.has(actor_id)
|
||||
and StringName(event.data["source_id"]) == SimulationIds.npc_inventory_id(actor_id)
|
||||
)
|
||||
if event_type == SimulationIds.EVENT_RESOURCE_EXTRACTED and actor_id == -1:
|
||||
var resource_state := (
|
||||
resource_records_by_id.get(StringName(event.data["source_id"])) as ResourceStateRecord
|
||||
)
|
||||
return (
|
||||
resource_state != null
|
||||
and resource_state.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
||||
and resource_state.can_player_use_resource()
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary:
|
||||
var migrated := legacy_data.duplicate(true)
|
||||
migrated["schema_version"] = SCHEMA_VERSION
|
||||
@@ -390,6 +559,10 @@ static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary
|
||||
migrated.get("economic_events", []),
|
||||
int((migrated.get("simulation", {}) as Dictionary).get("tick_count", 0))
|
||||
)
|
||||
migrated["opportunities"] = []
|
||||
var opportunity_simulation_data: Dictionary = migrated.get("simulation", {})
|
||||
opportunity_simulation_data["next_opportunity_id"] = 0
|
||||
migrated["simulation"] = opportunity_simulation_data
|
||||
return migrated
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user