diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/admin/static/itemeditor.js | 48 | ||||
| -rw-r--r-- | internal/behavior/behavior.go | 26 | ||||
| -rw-r--r-- | internal/behavior/store.go | 8 | ||||
| -rw-r--r-- | internal/behavior/types.go | 47 | ||||
| -rw-r--r-- | internal/game/act.go | 10 | ||||
| -rw-r--r-- | internal/game/act_clean.go | 2 | ||||
| -rw-r--r-- | internal/game/act_combine.go | 157 | ||||
| -rw-r--r-- | internal/game/act_fletch.go | 2 | ||||
| -rw-r--r-- | internal/game/act_gather.go | 25 | ||||
| -rw-r--r-- | internal/game/act_state.go | 8 | ||||
| -rw-r--r-- | internal/game/act_use.go | 28 | ||||
| -rw-r--r-- | internal/game/act_use_interaction.go | 23 | ||||
| -rw-r--r-- | internal/game/cmd_use.go | 32 | ||||
| -rw-r--r-- | internal/game/core_production.go | 7 | ||||
| -rw-r--r-- | internal/game/production_engine.go | 31 | ||||
| -rw-r--r-- | internal/item/item.go | 4 |
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"` |
