Skip to content

Commit

Permalink
feature: Actual faction-to-faction trading!
Browse files Browse the repository at this point in the history
fix: Less silly pricing
feature: Deferred pawn init for AIs (used to customize faction spawns for ^^^)
feature: Lookup-based needs (currently money)

Current setup is one hungry miner faction selling ore and buying food to a manufacturer. Prices swing wildly, but are not *totally* nuts. Factions don't produce anything yet.
  • Loading branch information
jmalek committed Aug 26, 2024
1 parent 1cca2d7 commit 23fe422
Show file tree
Hide file tree
Showing 22 changed files with 344 additions and 65 deletions.
Binary file modified GOAI/GOAI.dmb
Binary file not shown.
2 changes: 1 addition & 1 deletion GOAI/GOAI.dme
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
#include "integrations\utility_agent\senses\pathservice.dm"
#include "integrations\utility_agent\senses\vision.dm"
#include "integrations\utility_agent\systems\movement_system.dm"
#include "maps\trademap.dmm"
#include "maps\trademap2fac.dmm"
#include "utility\actions.dm"
#include "utility\actionset.dm"
#include "utility\actiontemplate.dm"
Expand Down
12 changes: 12 additions & 0 deletions GOAI/_datastructures/global_id.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@

/datum/proc/InitializeGlobalId(var/list/args = null)
/* Subclass hook in case the ID has to be dynamically generated */

if(src.global_id)
// Do not rewrite the ID if initialized
return src.global_id

// By default, generate an ID from a ref macro - guaranteed to be unique up to a reallocation.
var/default_id = ref(src)
src.global_id = default_id
return default_id


// Shorthand for a very common code pattern where the Global ID is lazily initialized.
// The OR operator is short-circuiting, so the initialization will only be done once, when needed,
// because the initialization proc is expected to set the new ID on the object as part of its contract.
#define GET_GLOBAL_ID_LAZY(TargetDatum) (TargetDatum.global_id || TargetDatum.InitializeGlobalId())
3 changes: 3 additions & 0 deletions GOAI/_need_keys.dm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

/* Materials */

// Unprocessed metal ores
#define NEED_ORE_GENERIC "ore"

// Raw steel, e.g. for construction
#define NEED_STEEL "steel"

Expand Down
24 changes: 24 additions & 0 deletions GOAI/basics/brain/concrete/utility/utility_brain_needs.dm
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
/datum/brain/utility/GetNeed(var/key, var/default = null)
// This part is copypasta'd from the parent; we could dedupe it,
// but it saves us a proc-call in the early exit cases.

if(isnull(src.needs))
return default

var/found = (key in src.needs)

if(!found)
return default

// This is the new bit
if(key == NEED_WEALTH)
var/datum/utility_ai/commander = src.GetAiController()

if(!istype(commander))
return default

return commander.GetWealthNeedFromAssets()

var/result = ..(key, default)
return result


/datum/brain/utility/SetNeed(var/key, var/val)
if(isnull(key))
Expand Down
6 changes: 6 additions & 0 deletions GOAI/goai_data/commodity_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
},
"/obj/decor": {
"prestige": 2
},
"/obj/ore": {
"ore": 5
},
"/obj/steel_bar": {
"steel": 1
}
}
39 changes: 39 additions & 0 deletions GOAI/goai_data/faction_definitions/FreeTrade.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "FreeTrade",
"tags": [
"FreeTrade"
],
"relationships": {
"ORMA": 50
},
"actionset_files": [
"faction_base.json",
"faction_trade_test.json"
],
"needs": {
"food_generic": 100,
"wealth": 4000,
"prestige": 50,
"ore": 5,
"steel": 80
},
"need_weights": {
"food_generic": 4,
"wealth": 3,
"prestige": 1,
"ore": 2,
"steel": 2
},
"preferred_trades": {
"food_generic": "/obj/food",
"prestige": "/obj/decor",
"ore": "/obj/ore",
"steel": "/obj/steel_bar"
},
"assets": {
"wealth": 4000,
"/obj/food": 80,
"/obj/ore": 5,
"/obj/steel_bar": 80
}
}
39 changes: 39 additions & 0 deletions GOAI/goai_data/faction_definitions/ORMA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "ORMA",
"tags": [
"ORMA"
],
"relationships": {
"FreeTrade": 50
},
"actionset_files": [
"faction_base.json",
"faction_trade_test.json"
],
"needs": {
"food_generic": 50,
"wealth": 1000,
"prestige": 50,
"ore": 80,
"steel": 50
},
"need_weights": {
"food_generic": 4,
"wealth": 3,
"prestige": 1,
"ore": 1,
"steel": 2
},
"preferred_trades": {
"food_generic": "/obj/food",
"prestige": "/obj/decor",
"ore": "/obj/ore",
"steel": "/obj/steel_bar"
},
"assets": {
"wealth": 1000,
"/obj/food": 10,
"/obj/ore": 800,
"/obj/steel_bar": 80
}
}
10 changes: 5 additions & 5 deletions GOAI/goai_data/smartobject_definitions/faction_trade_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"considerations": [
{
"name": "Offers Below Max",
"input_proc": "/proc/consideration_offers_pending",
"input_proc": "/proc/consideration_contracts_pending",
"lo_mark": 0,
"hi_mark": 2,
"curve_proc": "/proc/curve_antilinear",
Expand All @@ -45,7 +45,7 @@
"name": "TradeDesirability",
"input_proc": "/proc/consideration_trade_desirability",
"lo_mark": 0,
"hi_mark": 1,
"hi_mark": 0.2,
"curve_proc": "/proc/curve_linear"
}
]
Expand Down Expand Up @@ -90,9 +90,9 @@
{
"name": "Need Weight",
"input_proc": "/proc/consideration_input_get_need_weight",
"lo_mark": -10,
"hi_mark": 10,
"curve_proc": "/proc/curve_linear"
"lo_mark": 1,
"hi_mark": 20,
"curve_proc": "/proc/curve_antilinear"
}
]
},
Expand Down
33 changes: 25 additions & 8 deletions GOAI/integrations/spawners/factions.dm
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@

/proc/spawn_faction_commander(var/faction_name)
var/datum/utility_ai/faction_commander/debug/new_commander = new()
var/true_faction_name = (faction_name || BuildFactionName())
/proc/spawn_faction_commander(var/factionspec_filepath, var/generate_faction_name = FALSE)
var/datum/utility_ai/faction_commander/spawner/new_commander = new()

if(true_faction_name)
new_commander.name = true_faction_name
if(factionspec_filepath)
new_commander.factionspec_source = factionspec_filepath

// We use deferred InitPawn() here so we need to call it ourselves.
new_commander.InitPawn()

if(generate_faction_name)
new_commander.name = BuildFactionName()
else
new_commander.name = new_commander.GetPawn()?.name

return new_commander

Expand Down Expand Up @@ -37,14 +44,24 @@


/obj/spawner/oneshot/faction
var/faction_name
var/factionspec_filepath = null
var/generate_faction_name = null
script = /proc/spawn_faction_commander


/obj/spawner/oneshot/faction/CallScript()
set waitfor = FALSE

if(!active)
return

var/script_args = list(faction_name = BuildFactionName())
sleep(-1)
var/script_args = list()

if(!isnull(src.factionspec_filepath))
script_args["factionspec_filepath"] = src.factionspec_filepath

if(!isnull(src.generate_faction_name))
script_args["generate_faction_name"] = src.generate_faction_name

sleep(0)
call(script)(arglist(script_args))
21 changes: 21 additions & 0 deletions GOAI/integrations/trading/tests/trade_consideration_tests.dm
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ var/global/datum/faction_data/trade_debug_faction = null
to_chat(usr, "[raw_faction_ai] brain [faction_brain] is not a Utility brain, somehow")
return

var/datum/pawn = faction_ai.GetPawn()
var/has_pawn = istype(pawn)

var/list/new_needs = testdata["_new_needs"] || list()

ASSETS_TABLE_LAZY_INIT(TRUE)

for(var/new_need_key in new_needs)
var/new_need_val = new_needs[new_need_key]

Expand All @@ -41,10 +46,26 @@ var/global/datum/faction_data/trade_debug_faction = null

faction_brain.needs[new_need_key] = new_need_val

if(has_pawn && new_need_key == NEED_WEALTH)
var/list/target_assets = GET_ASSETS_TRACKER(pawn.global_id || pawn.InitializeGlobalId()) || list()
target_assets[NEED_WEALTH] = new_need_val
UPDATE_ASSETS_TRACKER(pawn.global_id, target_assets)

if(isnull(global.trade_debug_faction))
var/datum/faction_data/economy_gods = new("Economy Gods")
global.trade_debug_faction = economy_gods

// Give 'em near-infinite stuff to trade with
// This faction has no AI, any offers they make we create manually,
// so having infinite resources does not cause any problems for us.
var/list/economy_gods_assets = list(
"/obj/decor" = 1e6,
"/obj/ore" = 1e6,
"/obj/food" = 1e6,
NEED_WEALTH = 1e6
)
UPDATE_ASSETS_TRACKER((economy_gods.global_id || economy_gods.InitializeGlobalId()), economy_gods_assets)

for(var/test_key in testdata)
if(test_key == "_new_needs")
continue
Expand Down
7 changes: 5 additions & 2 deletions GOAI/integrations/trading/trade_contract.dm
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
src.on_failure_proc_args
)

to_world_log("Contract [contract] <[NULL_TO_TEXT(contract.id)]> ([contract.commodity_key] x [contract.commodity_amount] @ [contract.cash_value] by [contract.deadline]) created between creator [contract.creator] and contractor [contract.receiver]")
to_world_log("Contract [contract] <ID #[NULL_TO_TEXT(contract.id)]> ([contract.commodity_key] x [contract.commodity_amount] @ [contract.cash_value] by [NULL_TO_TEXT(contract.deadline)]) created between creator [contract.creator] and contractor [contract.receiver]")
return contract


Expand Down Expand Up @@ -188,23 +188,26 @@
ASSETS_TABLE_LAZY_INIT(TRUE)

if(isnull(party.global_id))
to_world_log("ERROR: EscrowPut contract receiver ([NULL_TO_TEXT(party)]) has no global ID.")
to_world_log("ERROR: EscrowPut party ([NULL_TO_TEXT(party)]) has no global ID.")
return null

var/list/assets = GET_ASSETS_TRACKER(party.global_id)

if(!assets)
// If we don't even have a list entry, we sure as hell don't have enough stuff to put here.
to_world_log("WARNING: EscrowPut party ([NULL_TO_TEXT(party)]) has no assets.")
return FALSE

var/owned_key_amt = assets[key]

if(!owned_key_amt)
// We ain't got it, abort.
to_world_log("WARNING: EscrowPut party ([NULL_TO_TEXT(party)]) has no asset '[key]' (assets: [json_encode(assets)]).")
return FALSE

if(owned_key_amt < value)
// We got some, but not enough - still abort.
to_world_log("WARNING: EscrowPut party ([NULL_TO_TEXT(party)]) has insufficient amount of [key] - needs [value], has [owned_key_amt].")
return FALSE

if(!istype(src.escrow))
Expand Down
3 changes: 0 additions & 3 deletions GOAI/integrations/trading/trade_selector_procs/data_driven.dm
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@

var/list/lookup = ai_brain.preferred_trades

#warn utility_best_trade_item_selector_personality_generic has debug logs
to_world_log("DEBUG: utility_best_trade_item_selector_personality_generic key is [need_key], lookup is [lookup && json_encode(lookup)]")

var/best_choice = lookup?[need_key]
return best_choice

Expand Down
Loading

0 comments on commit 23fe422

Please sign in to comment.