feat: add player standing and quest generation from village needs
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
class_name PlayerQuestRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_VERSION := 1
|
||||
const STATUS_OPEN := &"open"
|
||||
const STATUS_COMPLETED := &"completed"
|
||||
const STATUS_EXPIRED := &"expired"
|
||||
|
||||
var data: Dictionary
|
||||
|
||||
|
||||
func _init(record_data: Dictionary = {}) -> void:
|
||||
data = record_data.duplicate(true)
|
||||
|
||||
|
||||
static func create(
|
||||
quest_id: int,
|
||||
opportunity_id: int,
|
||||
requester_npc_id: int,
|
||||
quest_type: StringName,
|
||||
resource_id: StringName,
|
||||
target_id: StringName,
|
||||
target_amount: float,
|
||||
created_tick: int,
|
||||
standing_reward: float
|
||||
) -> PlayerQuestRecord:
|
||||
return (
|
||||
PlayerQuestRecord
|
||||
. new(
|
||||
{
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"quest_id": quest_id,
|
||||
"opportunity_id": opportunity_id,
|
||||
"requester_npc_id": requester_npc_id,
|
||||
"quest_type": String(quest_type),
|
||||
"resource_id": String(resource_id),
|
||||
"target_id": String(target_id),
|
||||
"target_amount": target_amount,
|
||||
"status": String(STATUS_OPEN),
|
||||
"created_tick": created_tick,
|
||||
"standing_reward": standing_reward,
|
||||
"resolution_event_id": -1,
|
||||
"resolved_tick": -1,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
static func from_dictionary(record_data: Dictionary) -> PlayerQuestRecord:
|
||||
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
|
||||
return null
|
||||
if not (
|
||||
record_data
|
||||
. has_all(
|
||||
[
|
||||
"quest_id",
|
||||
"opportunity_id",
|
||||
"requester_npc_id",
|
||||
"quest_type",
|
||||
"resource_id",
|
||||
"target_id",
|
||||
"target_amount",
|
||||
"status",
|
||||
"created_tick",
|
||||
"standing_reward",
|
||||
"resolution_event_id",
|
||||
"resolved_tick",
|
||||
]
|
||||
)
|
||||
):
|
||||
return null
|
||||
var quest_id := int(record_data["quest_id"])
|
||||
var opportunity_id := int(record_data["opportunity_id"])
|
||||
var requester_npc_id := int(record_data["requester_npc_id"])
|
||||
var quest_type := StringName(record_data["quest_type"])
|
||||
var resource_id := StringName(record_data["resource_id"])
|
||||
var target_id := StringName(record_data["target_id"])
|
||||
var target_amount := float(record_data["target_amount"])
|
||||
var status := StringName(record_data["status"])
|
||||
var created_tick := int(record_data["created_tick"])
|
||||
var standing_reward := float(record_data["standing_reward"])
|
||||
var resolution_event_id := int(record_data["resolution_event_id"])
|
||||
var resolved_tick := int(record_data["resolved_tick"])
|
||||
if (
|
||||
quest_id < 0
|
||||
or opportunity_id < 0
|
||||
or requester_npc_id < 0
|
||||
or quest_type.is_empty()
|
||||
or resource_id.is_empty()
|
||||
or target_id.is_empty()
|
||||
or not is_finite(target_amount)
|
||||
or target_amount <= 0.0
|
||||
or created_tick < 0
|
||||
or not is_finite(standing_reward)
|
||||
or standing_reward <= 0.0
|
||||
or resolution_event_id < -1
|
||||
):
|
||||
return null
|
||||
match status:
|
||||
STATUS_OPEN:
|
||||
if resolution_event_id != -1 or resolved_tick != -1:
|
||||
return null
|
||||
STATUS_COMPLETED:
|
||||
if resolution_event_id < 0 or resolved_tick < created_tick:
|
||||
return null
|
||||
STATUS_EXPIRED:
|
||||
if resolution_event_id != -1 or resolved_tick < created_tick:
|
||||
return null
|
||||
_:
|
||||
return null
|
||||
var record := create(
|
||||
quest_id,
|
||||
opportunity_id,
|
||||
requester_npc_id,
|
||||
quest_type,
|
||||
resource_id,
|
||||
target_id,
|
||||
target_amount,
|
||||
created_tick,
|
||||
standing_reward
|
||||
)
|
||||
if status == STATUS_COMPLETED:
|
||||
record.complete(resolution_event_id, resolved_tick)
|
||||
elif status == STATUS_EXPIRED:
|
||||
record.expire(resolved_tick)
|
||||
return record
|
||||
|
||||
|
||||
func get_quest_id() -> int:
|
||||
return int(data["quest_id"])
|
||||
|
||||
|
||||
func get_opportunity_id() -> int:
|
||||
return int(data["opportunity_id"])
|
||||
|
||||
|
||||
func get_requester_npc_id() -> int:
|
||||
return int(data["requester_npc_id"])
|
||||
|
||||
|
||||
func get_quest_type() -> StringName:
|
||||
return StringName(data["quest_type"])
|
||||
|
||||
|
||||
func get_resource_id() -> StringName:
|
||||
return StringName(data["resource_id"])
|
||||
|
||||
|
||||
func get_target_id() -> StringName:
|
||||
return StringName(data["target_id"])
|
||||
|
||||
|
||||
func get_target_amount() -> float:
|
||||
return float(data["target_amount"])
|
||||
|
||||
|
||||
func get_status() -> StringName:
|
||||
return StringName(data["status"])
|
||||
|
||||
|
||||
func get_created_tick() -> int:
|
||||
return int(data["created_tick"])
|
||||
|
||||
|
||||
func get_standing_reward() -> float:
|
||||
return float(data["standing_reward"])
|
||||
|
||||
|
||||
func get_resolution_event_id() -> int:
|
||||
return int(data["resolution_event_id"])
|
||||
|
||||
|
||||
func get_resolved_tick() -> int:
|
||||
return int(data["resolved_tick"])
|
||||
|
||||
|
||||
func is_open() -> bool:
|
||||
return get_status() == STATUS_OPEN
|
||||
|
||||
|
||||
func complete(resolution_event_id: int, resolved_tick: int) -> bool:
|
||||
if not is_open() or resolution_event_id < 0 or resolved_tick < get_created_tick():
|
||||
return false
|
||||
data["status"] = String(STATUS_COMPLETED)
|
||||
data["resolution_event_id"] = resolution_event_id
|
||||
data["resolved_tick"] = resolved_tick
|
||||
return true
|
||||
|
||||
|
||||
func expire(resolved_tick: int) -> bool:
|
||||
if not is_open() or resolved_tick < get_created_tick():
|
||||
return false
|
||||
data["status"] = String(STATUS_EXPIRED)
|
||||
data["resolved_tick"] = resolved_tick
|
||||
return true
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
return data.duplicate(true)
|
||||
@@ -0,0 +1 @@
|
||||
uid://rix0e3rygdoo
|
||||
@@ -0,0 +1,120 @@
|
||||
class_name PlayerStandingRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_VERSION := 1
|
||||
const MAX_STANDING := 100.0
|
||||
const TIER_STRANGER := 0
|
||||
const TIER_KNOWN_HAND := 1
|
||||
const TIER_TRUSTED := 2
|
||||
const TIER_VILLAGE_STEWARD := 3
|
||||
const TIER_VOICE_OF_JAJCE := 4
|
||||
|
||||
const TIER_THRESHOLDS := {
|
||||
TIER_STRANGER: 0.0,
|
||||
TIER_KNOWN_HAND: 15.0,
|
||||
TIER_TRUSTED: 35.0,
|
||||
TIER_VILLAGE_STEWARD: 60.0,
|
||||
TIER_VOICE_OF_JAJCE: 85.0,
|
||||
}
|
||||
|
||||
const TIER_NAMES := {
|
||||
TIER_STRANGER: "Stranger",
|
||||
TIER_KNOWN_HAND: "Known Hand",
|
||||
TIER_TRUSTED: "Trusted",
|
||||
TIER_VILLAGE_STEWARD: "Village Steward",
|
||||
TIER_VOICE_OF_JAJCE: "Voice of Jajce",
|
||||
}
|
||||
|
||||
var data: Dictionary
|
||||
|
||||
|
||||
func _init(record_data: Dictionary = {}) -> void:
|
||||
data = record_data.duplicate(true)
|
||||
|
||||
|
||||
static func create() -> PlayerStandingRecord:
|
||||
return (
|
||||
PlayerStandingRecord
|
||||
. new(
|
||||
{
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"standing": 0.0,
|
||||
"resolved_needs": 0,
|
||||
"gratitude": {},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
static func from_dictionary(record_data: Dictionary) -> PlayerStandingRecord:
|
||||
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
|
||||
return null
|
||||
if not record_data.has_all(["standing", "resolved_needs", "gratitude"]):
|
||||
return null
|
||||
var standing := float(record_data["standing"])
|
||||
var resolved_needs := int(record_data["resolved_needs"])
|
||||
var gratitude = record_data["gratitude"]
|
||||
if (
|
||||
not is_finite(standing)
|
||||
or standing < 0.0
|
||||
or standing > MAX_STANDING
|
||||
or resolved_needs < 0
|
||||
or not gratitude is Dictionary
|
||||
):
|
||||
return null
|
||||
var normalized_gratitude := {}
|
||||
for raw_npc_id in gratitude:
|
||||
var npc_id := int(raw_npc_id)
|
||||
var value := float(gratitude[raw_npc_id])
|
||||
if npc_id < 0 or not is_finite(value) or value < 0.0 or value > 1.0:
|
||||
return null
|
||||
normalized_gratitude[npc_id] = value
|
||||
return (
|
||||
PlayerStandingRecord
|
||||
. new(
|
||||
{
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"standing": standing,
|
||||
"resolved_needs": resolved_needs,
|
||||
"gratitude": normalized_gratitude,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func grant_standing(amount: float, requester_npc_id: int) -> void:
|
||||
if not is_finite(amount) or amount <= 0.0 or requester_npc_id < 0:
|
||||
return
|
||||
data["standing"] = minf(get_standing() + amount, MAX_STANDING)
|
||||
data["resolved_needs"] = int(data["resolved_needs"]) + 1
|
||||
var gratitude := get_gratitude(requester_npc_id)
|
||||
data["gratitude"][requester_npc_id] = minf(gratitude + 0.1, 1.0)
|
||||
|
||||
|
||||
func get_standing() -> float:
|
||||
return float(data["standing"])
|
||||
|
||||
|
||||
func get_resolved_needs() -> int:
|
||||
return int(data["resolved_needs"])
|
||||
|
||||
|
||||
func get_gratitude(npc_id: int) -> float:
|
||||
return float(data["gratitude"].get(npc_id, 0.0))
|
||||
|
||||
|
||||
func get_tier() -> int:
|
||||
var current := TIER_STRANGER
|
||||
for tier in [TIER_VOICE_OF_JAJCE, TIER_VILLAGE_STEWARD, TIER_TRUSTED, TIER_KNOWN_HAND]:
|
||||
if get_standing() >= float(TIER_THRESHOLDS[tier]):
|
||||
current = tier
|
||||
break
|
||||
return current
|
||||
|
||||
|
||||
func get_tier_name() -> String:
|
||||
return String(TIER_NAMES[get_tier()])
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
return data.duplicate(true)
|
||||
@@ -0,0 +1 @@
|
||||
uid://v827brnqruus
|
||||
@@ -2,7 +2,7 @@ class_name SimulationStateRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_NAME := "the_steward.simulation"
|
||||
const SCHEMA_VERSION := 11
|
||||
const SCHEMA_VERSION := 12
|
||||
const LEGACY_SCHEMA_VERSION := 1
|
||||
const EVENT_LEGACY_SCHEMA_VERSION := 2
|
||||
const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3
|
||||
@@ -13,6 +13,7 @@ const OPPORTUNITY_LEGACY_SCHEMA_VERSION := 8
|
||||
const ANIMAL_LEGACY_SCHEMA_VERSION := 9
|
||||
const ROUTINE_LEGACY_SCHEMA_VERSION := 10
|
||||
const PREVIOUS_SCHEMA_VERSION := 7
|
||||
const PLAYER_LEGACY_SCHEMA_VERSION := 11
|
||||
|
||||
var simulation: Dictionary
|
||||
var village: VillageStateRecord
|
||||
@@ -24,6 +25,8 @@ var economic_events: Array[EconomicEventRecord] = []
|
||||
var relationships: Array[RelationshipStateRecord] = []
|
||||
var event_knowledge: Array[KnownEventStateRecord] = []
|
||||
var opportunities: Array[OpportunityStateRecord] = []
|
||||
var player_standing: PlayerStandingRecord
|
||||
var player_quests: Array[PlayerQuestRecord] = []
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
@@ -52,6 +55,9 @@ func to_dictionary() -> Dictionary:
|
||||
var opportunity_data: Array[Dictionary] = []
|
||||
for opportunity_record in opportunities:
|
||||
opportunity_data.append(opportunity_record.to_dictionary())
|
||||
var player_quest_data: Array[Dictionary] = []
|
||||
for player_quest_record in player_quests:
|
||||
player_quest_data.append(player_quest_record.to_dictionary())
|
||||
|
||||
return {
|
||||
"schema": SCHEMA_NAME,
|
||||
@@ -65,7 +71,9 @@ func to_dictionary() -> Dictionary:
|
||||
"economic_events": event_data,
|
||||
"relationships": relationship_data,
|
||||
"event_knowledge": knowledge_data,
|
||||
"opportunities": opportunity_data
|
||||
"opportunities": opportunity_data,
|
||||
"player_standing": player_standing.to_dictionary(),
|
||||
"player_quests": player_quest_data
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +105,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
OPPORTUNITY_LEGACY_SCHEMA_VERSION,
|
||||
ANIMAL_LEGACY_SCHEMA_VERSION,
|
||||
ROUTINE_LEGACY_SCHEMA_VERSION,
|
||||
PLAYER_LEGACY_SCHEMA_VERSION,
|
||||
]
|
||||
):
|
||||
record_data = _migrate_legacy(record_data, version)
|
||||
@@ -116,6 +125,8 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
"relationships",
|
||||
"event_knowledge",
|
||||
"opportunities",
|
||||
"player_standing",
|
||||
"player_quests",
|
||||
]
|
||||
)
|
||||
):
|
||||
@@ -418,6 +429,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
record.relationships.append(relationship_record)
|
||||
|
||||
var opportunity_ids := {}
|
||||
var opportunity_records_by_id := {}
|
||||
var trigger_event_ids := {}
|
||||
var resolution_event_ids := {}
|
||||
var has_open_opportunity := false
|
||||
@@ -452,12 +464,58 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
|
||||
):
|
||||
return null
|
||||
opportunity_ids[opportunity_id] = true
|
||||
opportunity_records_by_id[opportunity_id] = opportunity_record
|
||||
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
|
||||
|
||||
var standing_data = record_data["player_standing"]
|
||||
if not standing_data is Dictionary:
|
||||
return null
|
||||
var standing_record := PlayerStandingRecord.from_dictionary(standing_data)
|
||||
if standing_record == null:
|
||||
return null
|
||||
record.player_standing = standing_record
|
||||
|
||||
var player_quest_data = record_data["player_quests"]
|
||||
if not player_quest_data is Array:
|
||||
return null
|
||||
var quest_ids := {}
|
||||
var quest_opportunity_ids := {}
|
||||
var highest_quest_id := -1
|
||||
for item in player_quest_data:
|
||||
if not item is Dictionary:
|
||||
return null
|
||||
var quest_record := PlayerQuestRecord.from_dictionary(item)
|
||||
if quest_record == null:
|
||||
return null
|
||||
var quest_id := quest_record.get_quest_id()
|
||||
var quest_opportunity_id := quest_record.get_opportunity_id()
|
||||
var requester_id := quest_record.get_requester_npc_id()
|
||||
if (
|
||||
quest_ids.has(quest_id)
|
||||
or quest_opportunity_ids.has(quest_opportunity_id)
|
||||
or not npc_ids.has(requester_id)
|
||||
or not opportunity_ids.has(quest_opportunity_id)
|
||||
):
|
||||
return null
|
||||
if (
|
||||
quest_record.get_status() == PlayerQuestRecord.STATUS_OPEN
|
||||
and not opportunity_records_by_id.has(quest_opportunity_id)
|
||||
):
|
||||
return null
|
||||
if (
|
||||
quest_record.get_resolution_event_id() >= 0
|
||||
and not event_ids.has(quest_record.get_resolution_event_id())
|
||||
):
|
||||
return null
|
||||
quest_ids[quest_id] = true
|
||||
quest_opportunity_ids[quest_opportunity_id] = true
|
||||
highest_quest_id = maxi(highest_quest_id, quest_id)
|
||||
record.player_quests.append(quest_record)
|
||||
|
||||
return record
|
||||
|
||||
|
||||
@@ -772,6 +830,12 @@ static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary
|
||||
var opportunity_simulation_data: Dictionary = migrated.get("simulation", {})
|
||||
opportunity_simulation_data["next_opportunity_id"] = 0
|
||||
migrated["simulation"] = opportunity_simulation_data
|
||||
if version <= PLAYER_LEGACY_SCHEMA_VERSION:
|
||||
migrated["player_standing"] = PlayerStandingRecord.create().to_dictionary()
|
||||
migrated["player_quests"] = []
|
||||
var quest_simulation_data: Dictionary = migrated.get("simulation", {})
|
||||
quest_simulation_data["next_quest_id"] = 0
|
||||
migrated["simulation"] = quest_simulation_data
|
||||
return migrated
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user