aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-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
11 files changed, 264 insertions, 61 deletions
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)
}