aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-01 03:21:52 -0400
committerhistoria <[not public]>2026-07-01 03:21:52 -0400
commitfbe5090ac3325ff8ea6989cdf4515c7f077c975b (patch)
tree1811ad9c6a596e7177bb2c42944c389c0a120066 /internal
parent5f37dc9b6f6b79da04da70ae0c33ec8d02998587 (diff)
downloadthehouseoficarus-fbe5090ac3325ff8ea6989cdf4515c7f077c975b.tar.gz
feat: item messaging standardized, custom messages for production paths implemented
Diffstat (limited to 'internal')
-rw-r--r--internal/admin/static/itemeditor.js48
-rw-r--r--internal/behavior/behavior.go26
-rw-r--r--internal/behavior/store.go8
-rw-r--r--internal/behavior/types.go47
-rw-r--r--internal/game/act.go10
-rw-r--r--internal/game/act_clean.go2
-rw-r--r--internal/game/act_combine.go157
-rw-r--r--internal/game/act_fletch.go2
-rw-r--r--internal/game/act_gather.go25
-rw-r--r--internal/game/act_state.go8
-rw-r--r--internal/game/act_use.go28
-rw-r--r--internal/game/act_use_interaction.go23
-rw-r--r--internal/game/cmd_use.go32
-rw-r--r--internal/game/core_production.go7
-rw-r--r--internal/game/production_engine.go31
-rw-r--r--internal/item/item.go4
16 files changed, 354 insertions, 104 deletions
diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js
index d02e7ab..d02945a 100644
--- a/internal/admin/static/itemeditor.js
+++ b/internal/admin/static/itemeditor.js
@@ -59,24 +59,24 @@ var SECTIONS = [
['success_base', 'Base', 'number', '', 'narrow'],
['success_per_level', 'Per Lvl', 'number', '', 'narrow'],
['success_cap', 'Cap', 'number', '', 'narrow'],
- ['start_message', 'Start Message', 'text', '', 'wide'],
- ['message', 'Message', 'text', '', 'wide'],
- ['fail_message', 'Fail Message', 'text', '', 'wide', function(item) { return item.type === 'cooking' || item.subtype === 'smelt'; }],
- ['end_message', 'End Message', 'text', '', 'wide'],
+ ['start_message', 'Start Message', 'text', 'e.g. You begin smithing %i1.', 'wide'],
+ ['success_message', 'Success Message', 'text', 'e.g. You smith a %n.', 'wide'],
+ ['fail_message', 'Fail Message', 'text', 'e.g. You accidentally burn the %i1.', 'wide', function(item) { return item.type === 'cooking' || item.subtype === 'smelt'; }],
+ ['end_message', 'End Message', 'text', 'e.g. You\'ve finished smithing.', 'wide'],
],
fieldGroups: [
['type', 'subtype', 'level', 'xp', 'wait', 'output_qty'],
{fields: ['station', 'tool'], right: true},
{label: 'Success', fields: ['success_base', 'success_per_level', 'success_cap', 'fail']},
- ['start_message', 'message', 'fail_message', 'end_message'],
+ ['start_message', 'success_message', 'fail_message', 'end_message'],
],
subSubtables: [
{label:'Ingredients', key:'ingredients', fields:[
['quantity', 'Qty', 'number', '1', 'narrow'],
]},
{label:'Steps', key:'steps', fields:[
- ['tick', 'Ticks', 'number', '', 'narrow'],
- ['message', 'Message', 'text', '', 'wide'],
+ ['delay', 'Delay (ticks)', 'number', '', 'narrow'],
+ ['message', 'Message', 'text', '', 'wide'],
]},
],
}
@@ -556,6 +556,8 @@ function renderSubFieldHTML(sec, stKey, idx, f, val) {
if (f[4] === 'narrow') extraCls = ' obj-narrow';
else if (f[4] === 'grow') extraCls = ' obj-grow';
if (f[4] === 'wide') extraStyle = ' style="flex:1 1 100%"';
+ var defaultPh = '';
+ if (f[3]) defaultPh = ' placeholder="' + escAttr(f[3]) + '"';
if (f[2] === 'search') {
var entity = f[6] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
@@ -573,7 +575,7 @@ function renderSubFieldHTML(sec, stKey, idx, f, val) {
sel += '</select></label>';
return sel;
}
- return '<label class="obj-field' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"></label>';
+ return '<label class="obj-field' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + defaultPh + '></label>';
}
function renderSubtable(sec, st, data) {
@@ -653,7 +655,7 @@ function renderSubtable(sec, st, data) {
if (sst.key === 'steps') {
var stepsArr = item[sst.key];
if (!stepsArr || !Array.isArray(stepsArr) || stepsArr.length === 0) {
- h += '<button class="btn btn-sm obj-sub-add" style="margin-top:4px" onclick="addSubSubRow(\'' + sec.id + '\',\'' + st.key + '\',' + idx + ',\'' + sst.key + '\')">+ Add Custom Delay/Message</button>';
+ h += '<button class="btn btn-sm obj-sub-add" data-section="steps-add-' + idx + '" style="margin-top:4px" onclick="addSubSubRow(\'' + sec.id + '\',\'' + st.key + '\',' + idx + ',\'' + sst.key + '\')">+ Add Custom Delay/Message</button>';
return;
}
}
@@ -721,7 +723,9 @@ function removeConsumePair(secID, subKey, rowIdx, subSubKey, subIdx, pairIdx) {
function renderSubSubtable(sec, st, rowIdx, sst, parentItem) {
var arr = parentItem[sst.key] || [];
var isIngredients = sst.key === 'ingredients';
- var h = '<div class="obj-subtable" style="margin:6px 0 6px 12px;border-left:2px solid var(--accent)">';
+ var isSteps = sst.key === 'steps';
+ var stepsAttr = isSteps ? ' data-section="steps-section"' : '';
+ var h = '<div class="obj-subtable"' + stepsAttr + ' style="margin:6px 0 6px 12px;border-left:2px solid var(--accent)">';
if (!isIngredients) {
h += '<div class="obj-subtable-header">' + esc(sst.label) + '</div>';
}
@@ -1035,6 +1039,11 @@ function setupCraftTypeChange() {
var xpField = document.getElementById(id.replace(/_type$/, '_xp'));
var stationField = document.getElementById(id.replace(/_type$/, '_station'));
+ var combineHideFields = ['success_message', 'fail_message', 'tool', 'wait',
+ 'success_base', 'success_per_level', 'success_cap', 'fail'];
+
+ var recipeRow = typeSelect.closest('.obj-sub-row');
+
function update() {
var t = typeSelect.value;
@@ -1049,6 +1058,25 @@ function setupCraftTypeChange() {
if (sLbl) sLbl.style.display = (t === 'fletching' || t === 'combine' || t === 'pharmacy') ? 'none' : '';
}
+ combineHideFields.forEach(function(fKey) {
+ var el = document.getElementById(id.replace(/_type$/, '_' + fKey));
+ if (!el) return;
+ var lbl = el.closest('.obj-field');
+ if (lbl) lbl.style.display = t === 'combine' ? 'none' : '';
+ });
+
+ var successGroup = document.querySelector('.success-group');
+ if (successGroup) successGroup.style.display = t === 'combine' ? 'none' : '';
+ var successBtn = document.getElementById(successGroup ? successGroup.id + '_btn' : '');
+ if (successBtn) successBtn.style.display = t === 'combine' ? 'none' : '';
+
+ var stepsSection = recipeRow.querySelector('[data-section="steps-section"]');
+ var stepsAddBtn = recipeRow.querySelector('[data-section^="steps-add-"]');
+
+ var showSteps = t === 'combine';
+ if (stepsSection) stepsSection.style.display = showSteps ? '' : 'none';
+ if (stepsAddBtn) stepsAddBtn.style.display = showSteps ? '' : 'none';
+
if (subtypeSel) {
var sLbl = subtypeSel.closest('.obj-field');
if (t === 'smithing') {
diff --git a/internal/behavior/behavior.go b/internal/behavior/behavior.go
index 8333a05..c0a714c 100644
--- a/internal/behavior/behavior.go
+++ b/internal/behavior/behavior.go
@@ -5,15 +5,16 @@ type GatherConfig struct {
Tools []string `yaml:"tools"`
Bait string `yaml:"bait"`
Success SuccessFormula `yaml:"success"`
- GatherMsg string `yaml:"gather_message"`
+ GatherMessage string `yaml:"gather_message"`
DepletedMessage string `yaml:"depleted_message"`
ExhaustedMessage string `yaml:"exhausted_message"`
- FailMsg string `yaml:"fail_message"`
+ FailMessage string `yaml:"fail_message"`
Drops []DropEntry `yaml:"drops"`
RespawnTimer float64 `yaml:"respawn_timer"`
RespawnBroadcast string `yaml:"respawn_broadcast"`
DepleteTimer float64 `yaml:"deplete_timer"`
NestChance int `yaml:"nest_chance"`
+ BroadcastMessage string `yaml:"broadcast_message"`
}
type SuccessFormula struct {
@@ -82,13 +83,16 @@ type Condition struct {
}
type UseConfig struct {
- Message string `yaml:"message"`
- Wait float64 `yaml:"wait"`
- Consume map[string]int `yaml:"consume"`
- Reward DropEntry `yaml:"reward"`
- FailMsg string `yaml:"fail_message"`
- Success *SuccessFormula `yaml:"success"`
- Skill string `yaml:"skill"`
- Level int `yaml:"level"`
- XP int `yaml:"xp"`
+ StartMessage string `yaml:"start_message"`
+ Wait float64 `yaml:"wait"`
+ Consume map[string]int `yaml:"consume"`
+ Reward DropEntry `yaml:"reward"`
+ FailMessage string `yaml:"fail_message"`
+ Success *SuccessFormula `yaml:"success"`
+ Skill string `yaml:"skill"`
+ Level int `yaml:"level"`
+ XP int `yaml:"xp"`
+ SuccessMessage string `yaml:"success_message"`
+ EndMessage string `yaml:"end_message"`
+ BroadcastMessage string `yaml:"broadcast_message"`
}
diff --git a/internal/behavior/store.go b/internal/behavior/store.go
index 710506e..38cc72b 100644
--- a/internal/behavior/store.go
+++ b/internal/behavior/store.go
@@ -154,10 +154,10 @@ func ResolveDrop(dataDir string, drops []DropEntry) *DropEntry {
qty = 1
}
return &DropEntry{
- ItemID: resolved.ItemID,
- Quantity: qty,
- Depletes: drops[i].Depletes,
- Message: drops[i].Message,
+ ItemID: resolved.ItemID,
+ Quantity: qty,
+ Depletes: drops[i].Depletes,
+ SuccessMessage: drops[i].SuccessMessage,
}
}
}
diff --git a/internal/behavior/types.go b/internal/behavior/types.go
index c061742..571f2c4 100644
--- a/internal/behavior/types.go
+++ b/internal/behavior/types.go
@@ -55,6 +55,7 @@ const (
TypeMix ActionType = "mix"
TypeConstruct ActionType = "construct"
TypeTriggerModule ActionType = "trigger_module"
+ TypeUseInteraction ActionType = "use_interaction"
)
type Action struct {
@@ -85,14 +86,12 @@ type GatherData struct {
}
type ProductionData struct {
- ItemID string
- Phase int
- Wait float64
- StartMsg string
- EndMsg string
- Remaining int
- NextStepIndex int
- StepsAccumulatedTicks float64
+ ItemID string
+ Phase int
+ Wait float64
+ StartMessage string
+ EndMessage string
+ Remaining int
}
type StealData struct {
@@ -126,6 +125,12 @@ type UseData struct {
Step int
}
+type UseInteractionData struct {
+ Message string
+ Action *NodeAction
+ ObjName string
+}
+
type BurnData struct {
ItemID string
ToolSpeed float64
@@ -172,6 +177,16 @@ type SearchData struct {
Started bool
}
+type CombineData struct {
+ ItemID string
+ Phase int
+ StepIndex int
+ Remaining int
+ StartMessage string
+ EndMessage string
+ Wait float64
+}
+
type FletchData struct {
ItemID string
Phase int
@@ -196,14 +211,14 @@ type TriggerModuleData struct {
}
type DropEntry struct {
- ItemID string `yaml:"item_id"`
- Table string `yaml:"table"`
- Weight int `yaml:"weight"`
- Quantity int `yaml:"quantity"`
- Depletes bool `yaml:"depletes"`
- Message string `yaml:"message"`
- Level int `yaml:"level"`
- XP int `yaml:"xp"`
+ ItemID string `yaml:"item_id"`
+ Table string `yaml:"table"`
+ Weight int `yaml:"weight"`
+ Quantity int `yaml:"quantity"`
+ Depletes bool `yaml:"depletes"`
+ SuccessMessage string `yaml:"success_message"`
+ Level int `yaml:"level"`
+ XP int `yaml:"xp"`
}
type DropTableDef struct {
diff --git a/internal/game/act.go b/internal/game/act.go
index aa25625..c8101ab 100644
--- a/internal/game/act.go
+++ b/internal/game/act.go
@@ -236,9 +236,13 @@ func (g *Game) AdvanceActions() {
g.advanceObstacle(sess, p)
case behavior.TypeTalk:
g.advanceTalk(sess, p)
- case behavior.TypeTriggerModule:
- g.advanceTriggerModule(sess, p)
- default:
+ case behavior.TypeTriggerModule:
+ g.advanceTriggerModule(sess, p)
+ case behavior.TypeUseInteraction:
+ g.advanceUseInteraction(sess, p)
+ case behavior.TypeCombine:
+ g.advanceCombine(sess, p)
+ default:
if productionActionTypes[string(p.Action.Type)] {
g.advanceProduction(sess, p)
}
diff --git a/internal/game/act_clean.go b/internal/game/act_clean.go
index 021a07c..7647f4f 100644
--- a/internal/game/act_clean.go
+++ b/internal/game/act_clean.go
@@ -71,7 +71,7 @@ func (g *Game) advanceClean(sess *net.Session, p *player.Player) {
}
g.AccountStore.SaveCharacter(p)
- msg := g.resolveCraftMessage(sess, craft.Message, "You clean the %i1.", craft, matchItem.ID, nil, p.HasItem)
+ msg := g.resolveCraftMessage(sess, craft.SuccessMessage, "You clean the %i1.", craft, matchItem.ID, nil, p.HasItem)
if p.OptionBool("xp_drops") && craft.XP > 0 {
msg += g.formatXpDropSingle(sess, p, player.Pharmacy, craft.XP)
}
diff --git a/internal/game/act_combine.go b/internal/game/act_combine.go
new file mode 100644
index 0000000..8937d38
--- /dev/null
+++ b/internal/game/act_combine.go
@@ -0,0 +1,157 @@
+package game
+
+import (
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/item"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) startCombine(sess *net.Session, p *player.Player, item *item.ItemDef, craft *item.CraftDef, count int) {
+ g.cancelAction(p)
+ g.cancelBgAction(p)
+
+ if !craftHasAllItemsQty(craft, p.CountItem) {
+ sess.WriteLine("You don't have the required materials.")
+ g.reprompt(sess)
+ return
+ }
+
+ outDef, _ := g.ItemStore.Load(item.ID)
+ outputName := item.ID
+ if outDef != nil {
+ outputName = outDef.Name
+ }
+
+ startMsg := g.resolveCraftMessage(sess, craft.StartMessage, "You start combining %i1.", craft, item.ID, nil, p.HasItem)
+ endMsg := g.resolveCraftMessage(sess, craft.EndMessage, "You've finished combining.", craft, item.ID, nil, p.HasItem)
+
+ p.Action = &behavior.Action{
+ Type: behavior.TypeCombine,
+ TargetID: item.ID,
+ TargetName: outputName,
+ Data: &behavior.CombineData{
+ ItemID: item.ID,
+ Phase: 0,
+ StepIndex: 0,
+ Remaining: count,
+ StartMessage: startMsg,
+ EndMessage: endMsg,
+ Wait: craft.Wait,
+ },
+ WaitLeft: engine.ToTicks(1),
+ }
+
+ g.broadcastAction(sess, "%s starts combining.", p.Name)
+}
+
+func (g *Game) advanceCombine(sess *net.Session, p *player.Player) {
+ d, ok := p.Action.Data.(*behavior.CombineData)
+ if !ok {
+ g.cancelAction(p)
+ return
+ }
+
+ item := g.loadCraftItem(d.ItemID)
+ if item == nil {
+ g.cancelAction(p)
+ return
+ }
+ craft := item.FirstCraft()
+
+ switch d.Phase {
+ case 0:
+ if d.StartMessage != "" {
+ sess.WriteLine(d.StartMessage)
+ }
+ d.Phase = 1
+ if len(craft.Steps) > 0 {
+ p.Action.WaitLeft = engine.ToTicks(craft.Steps[0].Delay)
+ } else {
+ firstWait := craft.Wait
+ if firstWait <= 0 {
+ firstWait = 4
+ }
+ p.Action.WaitLeft = engine.ToTicks(firstWait)
+ }
+ return
+
+ case 1:
+ if len(craft.Steps) > 0 && d.StepIndex < len(craft.Steps) {
+ step := craft.Steps[d.StepIndex]
+ sess.WriteLine(g.resolveCraftVar(sess, step.Message, craft, item.ID, nil, p.HasItem))
+ d.StepIndex++
+ if d.StepIndex < len(craft.Steps) {
+ p.Action.WaitLeft = engine.ToTicks(craft.Steps[d.StepIndex].Delay)
+ return
+ }
+ }
+ d.Phase = 2
+ p.Action.WaitLeft = engine.ToTicks(1)
+ return
+
+ case 2:
+ if !craftHasAllItemsQty(craft, p.CountItem) {
+ if d.EndMessage != "" {
+ sess.WriteLine(d.EndMessage)
+ }
+ g.cancelAction(p)
+ return
+ }
+
+ consumeIngredients(craft, p.HasItem, p.RemoveItem)
+
+ outputQty := craft.OutputQty
+ if outputQty <= 0 {
+ outputQty = 1
+ }
+
+ outDef, _ := g.ItemStore.Load(item.ID)
+ placed := false
+ if outDef != nil && outDef.Stackable {
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == item.ID {
+ slot.Quantity += outputQty
+ placed = true
+ break
+ }
+ }
+ }
+ if !placed {
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ sess.WriteLine("Your inventory is too full!")
+ g.cancelAction(p)
+ return
+ }
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: item.ID, Quantity: outputQty})
+ }
+
+ g.AccountStore.SaveCharacter(p)
+
+ if d.Remaining > 0 {
+ d.Remaining--
+ if d.Remaining <= 0 {
+ if d.EndMessage != "" {
+ sess.WriteLine(d.EndMessage)
+ }
+ g.cancelAction(p)
+ return
+ }
+ }
+
+ if !craftHasAllItemsQty(craft, p.CountItem) {
+ if d.EndMessage != "" {
+ sess.WriteLine(d.EndMessage)
+ }
+ g.cancelAction(p)
+ return
+ }
+
+ d.Phase = 0
+ d.StepIndex = 0
+ p.Action.WaitLeft = engine.ToTicks(1)
+ }
+}
diff --git a/internal/game/act_fletch.go b/internal/game/act_fletch.go
index 800bc8e..ce3a525 100644
--- a/internal/game/act_fletch.go
+++ b/internal/game/act_fletch.go
@@ -188,7 +188,7 @@ func (g *Game) advanceFletch(sess *net.Session, p *player.Player) {
}
g.AccountStore.SaveCharacter(p)
- msg := g.resolveCraftMessage(sess, c.Message, "You fletch a %n.", c, def.ID, nil, p.HasItem)
+ msg := g.resolveCraftMessage(sess, c.SuccessMessage, "You fletch a %n.", c, def.ID, nil, p.HasItem)
if p.OptionBool("xp_drops") && c.XP > 0 {
msg += g.formatXpDropSingle(sess, p, skill, c.XP)
}
diff --git a/internal/game/act_gather.go b/internal/game/act_gather.go
index ab59de0..c34c6ae 100644
--- a/internal/game/act_gather.go
+++ b/internal/game/act_gather.go
@@ -106,7 +106,12 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
verb = "chopping"
}
- g.broadcastAction(sess, "%s starts %s the %s.", p.Name, verb, obj.Name)
+ if cfg.BroadcastMessage != "" {
+ bcMsg := strings.ReplaceAll(cfg.BroadcastMessage, "%o", obj.Name)
+ g.broadcastAction(sess, bcMsg, p.Name)
+ } else {
+ g.broadcastAction(sess, "%s starts %s the %s.", p.Name, verb, obj.Name)
+ }
d := &behavior.GatherData{
ObjDefID: obj.ID,
@@ -158,7 +163,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
return
}
}
- sess.WriteLine(fmt.Sprintf("%s", cfg.GatherMsg))
+ sess.WriteLine(fmt.Sprintf("%s", cfg.GatherMessage))
d.Step = 1
p.Action.WaitLeft = engine.ToTicks(wait)
return
@@ -220,12 +225,14 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
}
g.AccountStore.SaveCharacter(p)
- msg := drop.Message
- if msg == "" {
- msg = fmt.Sprintf("You manage to get some %s.", g.itemColorize(sess, itemDef, itemName))
- } else {
- msg = color.ExpandTags(g.colorMode(sess), msg)
- }
+ msg := drop.SuccessMessage
+ coloredName := g.itemColorize(sess, itemDef, itemName)
+ if msg == "" {
+ msg = fmt.Sprintf("You manage to get some %s.", coloredName)
+ } else {
+ msg = strings.ReplaceAll(msg, "%n", coloredName)
+ msg = color.ExpandTags(g.colorMode(sess), msg)
+ }
if xp > 0 && p.OptionBool("xp_drops") {
msg += g.formatXpDropSingle(sess, p, player.SkillName(cfg.Skill), xp)
}
@@ -294,7 +301,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
}
}
- sess.WriteLine(cfg.FailMsg)
+ sess.WriteLine(cfg.FailMessage)
if p.FirstFreeSlot() == -1 {
sess.WriteLine(g.colorize(sess, "error", "Your inventory is too full!"))
g.cancelAction(p)
diff --git a/internal/game/act_state.go b/internal/game/act_state.go
index 051390f..c4ccaba 100644
--- a/internal/game/act_state.go
+++ b/internal/game/act_state.go
@@ -81,9 +81,11 @@ func (g *Game) playerActionDisplay(p *player.Player) string {
return "mixing some " + a.TargetName
case behavior.TypeConstruct:
return "constructing some " + a.TargetName
- case behavior.TypeTriggerModule:
- return "triggering " + a.TargetName
- }
+ case behavior.TypeTriggerModule:
+ return "triggering " + a.TargetName
+ case behavior.TypeUseInteraction:
+ return "using " + a.TargetName
+ }
}
if a := p.BackgroundAction; a != nil {
diff --git a/internal/game/act_use.go b/internal/game/act_use.go
index 0d8582d..1e9d8e9 100644
--- a/internal/game/act_use.go
+++ b/internal/game/act_use.go
@@ -3,6 +3,7 @@ package game
import (
"fmt"
"math/rand"
+ "strings"
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/engine"
@@ -34,7 +35,11 @@ func (g *Game) startUse(sess *net.Session, p *player.Player, obj *object.ObjectD
return
}
- g.broadcastAction(sess, "%s uses the %s.", p.Name, obj.Name)
+ if cfg.BroadcastMessage != "" {
+ g.broadcastAction(sess, strings.ReplaceAll(cfg.BroadcastMessage, "%o", obj.Name), p.Name)
+ } else {
+ g.broadcastAction(sess, "%s uses the %s.", p.Name, obj.Name)
+ }
p.Action = &behavior.Action{
Type: "use",
@@ -44,7 +49,7 @@ func (g *Game) startUse(sess *net.Session, p *player.Player, obj *object.ObjectD
Data: &behavior.UseData{Cfg: cfg, Step: 0},
}
- sess.WriteLine(fmt.Sprintf("%s", cfg.Message))
+ sess.WriteLine(fmt.Sprintf("%s", cfg.StartMessage))
}
func (g *Game) advanceUse(sess *net.Session, p *player.Player) {
@@ -76,8 +81,8 @@ func (g *Game) advanceUse(sess *net.Session, p *player.Player) {
skillLevel := p.Level(player.SkillName(cfg.Skill))
chance := behavior.SuccessChance(*cfg.Success, skillLevel, cfg.Level)
if rand.Float64() >= chance {
- if cfg.FailMsg != "" {
- sess.WriteLine(cfg.FailMsg)
+ if cfg.FailMessage != "" {
+ sess.WriteLine(cfg.FailMessage)
}
p.Action.WaitLeft = engine.ToTicks(cfg.Wait)
return
@@ -107,17 +112,30 @@ func (g *Game) advanceUse(sess *net.Session, p *player.Player) {
if rewardDef != nil {
itemName = rewardDef.Name
}
- line := fmt.Sprintf("You make a %s.", g.itemColorize(sess, rewardDef, itemName))
+ coloredName := g.itemColorize(sess, rewardDef, itemName)
+
+ var line string
+ if cfg.SuccessMessage != "" {
+ line = strings.ReplaceAll(cfg.SuccessMessage, "%n", coloredName)
+ } else {
+ line = fmt.Sprintf("You make a %s.", coloredName)
+ }
if cfg.XP > 0 && p.OptionBool("xp_drops") {
line += g.formatXpDropSingle(sess, p, player.SkillName(cfg.Skill), cfg.XP)
}
sess.WriteLine(line)
if p.FirstFreeSlot() == -1 {
+ if cfg.EndMessage != "" {
+ sess.WriteLine(cfg.EndMessage)
+ }
sess.WriteLine("Your inventory is full.")
g.cancelAction(p)
return
}
+ if cfg.EndMessage != "" {
+ sess.WriteLine(cfg.EndMessage)
+ }
p.Action.WaitLeft = engine.ToTicks(cfg.Wait)
}
diff --git a/internal/game/act_use_interaction.go b/internal/game/act_use_interaction.go
new file mode 100644
index 0000000..b96aaa3
--- /dev/null
+++ b/internal/game/act_use_interaction.go
@@ -0,0 +1,23 @@
+package game
+
+import (
+ "thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) advanceUseInteraction(sess *net.Session, p *player.Player) {
+ d, ok := p.Action.Data.(*behavior.UseInteractionData)
+ if !ok {
+ g.cancelAction(p)
+ return
+ }
+ if d.Message != "" {
+ sess.WriteLine(d.Message)
+ }
+ if d.Action != nil {
+ g.applyNodeAction(sess, d.Action)
+ }
+ g.broadcastAction(sess, "%s uses the %s.", p.Name, d.ObjName)
+ g.cancelAction(p)
+}
diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go
index 033fd62..1989a3e 100644
--- a/internal/game/cmd_use.go
+++ b/internal/game/cmd_use.go
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
+ "thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/item"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
@@ -97,13 +98,17 @@ func (g *Game) useRoomObject(sess *net.Session, p *player.Player, input string)
if ui.Condition != nil && !g.checkCondition(sess, ui.Condition) {
continue
}
- if ui.Message != "" {
- sess.WriteLine(ui.Message)
+ g.cancelAction(p)
+ p.Action = &behavior.Action{
+ Type: behavior.TypeUseInteraction,
+ TargetName: def.Name,
+ WaitLeft: 1,
+ Data: &behavior.UseInteractionData{
+ Message: ui.Message,
+ Action: ui.Action,
+ ObjName: def.Name,
+ },
}
- if ui.Action != nil {
- g.applyNodeAction(sess, ui.Action)
- }
- g.broadcastAction(sess, "%s uses the %s.", p.Name, def.Name)
return true
}
if len(def.UseInteractions) > 0 {
@@ -286,11 +291,16 @@ func (g *Game) doUseItemOnTarget(sess *net.Session, p *player.Player, itemAName,
if ui.Condition != nil && !g.checkCondition(sess, ui.Condition) {
continue
}
- if ui.Message != "" {
- sess.WriteLine(ui.Message)
- }
- if ui.Action != nil {
- g.applyNodeAction(sess, ui.Action)
+ g.cancelAction(p)
+ p.Action = &behavior.Action{
+ Type: behavior.TypeUseInteraction,
+ TargetName: def.Name,
+ WaitLeft: 1,
+ Data: &behavior.UseInteractionData{
+ Message: ui.Message,
+ Action: ui.Action,
+ ObjName: def.Name,
+ },
}
return
}
diff --git a/internal/game/core_production.go b/internal/game/core_production.go
index da9466f..e6a62d4 100644
--- a/internal/game/core_production.go
+++ b/internal/game/core_production.go
@@ -80,13 +80,6 @@ var productionTypes = map[string]productionTypeInfo{
SuccessMessage: "You craft a %n.",
EndMessage: "You've finished crafting.",
},
- "combine": {
- ActionType: behavior.TypeCombine,
- DisplayVerb: "combining",
- StartMessage: "You start combining %i1.",
- SuccessMessage: "You produce %n.",
- EndMessage: "You've finished combining.",
- },
"fletching": {
ActionType: behavior.TypeFletch,
DisplayVerb: "fletching",
diff --git a/internal/game/production_engine.go b/internal/game/production_engine.go
index 048e152..1d54714 100644
--- a/internal/game/production_engine.go
+++ b/internal/game/production_engine.go
@@ -113,8 +113,8 @@ func (g *Game) startProduction(sess *net.Session, p *player.Player, item *item.I
ItemID: item.ID,
Phase: 0,
Wait: wait,
- StartMsg: startMsg,
- EndMsg: endMsg,
+ StartMessage: startMsg,
+ EndMessage: endMsg,
Remaining: count,
},
WaitLeft: engine.ToTicks(1),
@@ -129,6 +129,11 @@ func (g *Game) startProductionFromItem(sess *net.Session, p *player.Player, item
return
}
+ if craft.Type == "combine" {
+ g.startCombine(sess, p, item, craft, count)
+ return
+ }
+
_, stationName := g.findStation(p.RoomID, craft.Station)
if stationName == "" {
stationName = "inventory"
@@ -177,8 +182,8 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
itemID := d.ItemID
phase := d.Phase
wait := d.Wait
- startMsg := d.StartMsg
- endMsg := d.EndMsg
+ startMsg := d.StartMessage
+ endMsg := d.EndMessage
remaining := d.Remaining
item := g.loadCraftItem(itemID)
@@ -192,27 +197,11 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
if startMsg != "" {
sess.WriteLine(startMsg)
}
- if len(craft.Steps) > 0 {
- d.NextStepIndex = 0
- d.StepsAccumulatedTicks = 0
- }
d.Phase = 1
p.Action.WaitLeft = engine.ToTicks(wait)
return true
}
- if stepsIdx := d.NextStepIndex; stepsIdx < len(craft.Steps) {
- accTicks := d.StepsAccumulatedTicks
- accTicks += wait
- d.StepsAccumulatedTicks = accTicks
- for i := stepsIdx; i < len(craft.Steps); i++ {
- if accTicks >= craft.Steps[i].Tick {
- sess.WriteLine(g.resolveCraftVar(sess, craft.Steps[i].Message, craft, item.ID, nil, p.HasItem))
- d.NextStepIndex = i + 1
- }
- }
- }
-
skill := craft.EffectiveSkill()
skillLevel := p.Level(player.SkillName(skill))
chance := 1.0
@@ -278,7 +267,7 @@ func (g *Game) advanceProduction(sess *net.Session, p *player.Player) bool {
} else {
defaultSuccess = "You produce %n."
}
- msg := g.resolveCraftMessage(sess, craft.Message, defaultSuccess, craft, item.ID, byproducts, p.HasItem)
+ msg := g.resolveCraftMessage(sess, craft.SuccessMessage, defaultSuccess, craft, item.ID, byproducts, p.HasItem)
if p.OptionBool("xp_drops") && craft.XP > 0 {
msg += g.formatXpDropSingle(sess, p, player.SkillName(skill), craft.XP)
}
diff --git a/internal/item/item.go b/internal/item/item.go
index 0e63156..9c753b1 100644
--- a/internal/item/item.go
+++ b/internal/item/item.go
@@ -265,7 +265,7 @@ type IngredientEntry struct {
}
type CraftStep struct {
- Tick float64 `yaml:"tick"`
+ Delay float64 `yaml:"delay"`
Message string `yaml:"message"`
}
@@ -286,7 +286,7 @@ type CraftDef struct {
Ingredients []IngredientEntry `yaml:"ingredients"`
OutputQty int `yaml:"output_qty"`
Fail string `yaml:"fail"`
- Message string `yaml:"message"`
+ SuccessMessage string `yaml:"success_message"`
FailMessage string `yaml:"fail_message"`
StartMessage string `yaml:"start_message"`
EndMessage string `yaml:"end_message"`