aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-19 02:51:05 -0400
committerhistoria <[not public]>2026-06-19 02:51:05 -0400
commitdea4a06764c507699cf9adcd1fd0a572d227aff0 (patch)
tree25769809d34db70cf0bec7d8c6d3422c23780a55 /internal/game
parent2ae4a052fd1c15c3c6ba0f1dd324d896371aaffc (diff)
downloadthehouseoficarus-dea4a06764c507699cf9adcd1fd0a572d227aff0.tar.gz
feat: scavenge/scrap system started
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/action.go2
-rw-r--r--internal/game/action_identify.go80
-rw-r--r--internal/game/action_state.go7
-rw-r--r--internal/game/cmd_id.go144
-rw-r--r--internal/game/game.go6
5 files changed, 236 insertions, 3 deletions
diff --git a/internal/game/action.go b/internal/game/action.go
index 641f4dd..2d30010 100644
--- a/internal/game/action.go
+++ b/internal/game/action.go
@@ -233,6 +233,8 @@ func (g *Game) AdvanceActions() {
g.advanceStoke(sess, p)
case "search":
g.advanceSearch(sess, p)
+ case "identify":
+ g.advanceIdentify(sess, p)
default:
if productionActionTypes[p.Action.Type] {
g.advanceProduction(sess, p)
diff --git a/internal/game/action_identify.go b/internal/game/action_identify.go
new file mode 100644
index 0000000..3f63e62
--- /dev/null
+++ b/internal/game/action_identify.go
@@ -0,0 +1,80 @@
+package game
+
+import (
+ "fmt"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) advanceIdentify(sess *net.Session, p *player.Player) {
+ junkID := p.Action.Data["junk_id"].(string)
+ xpPer := p.Action.Data["xp_per"].(int)
+
+ scrapCount := p.CountItem("scrap_metal")
+ if scrapCount == 0 {
+ sess.WriteLine("You don't have any scrap metal.")
+ g.CancelAction(p)
+ return
+ }
+
+ scavLevel := p.Level(player.Scavenging)
+ multiplier := junkMultiplier(junkID, scavLevel)
+ totalJunk := scrapCount * multiplier
+
+ hasExistingStack := false
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == junkID {
+ hasExistingStack = true
+ break
+ }
+ }
+ if !hasExistingStack && p.FirstFreeSlot() == -1 {
+ sess.WriteLine("Your inventory is too full!")
+ g.CancelAction(p)
+ return
+ }
+
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == "scrap_metal" {
+ p.SetInvSlot(i, nil)
+ }
+ }
+
+ placed := false
+ for i := 0; i < 28; i++ {
+ slot := p.InvSlot(i)
+ if slot != nil && slot.ItemID == junkID {
+ slot.Quantity += totalJunk
+ placed = true
+ break
+ }
+ }
+ if !placed {
+ freeSlot := p.FirstFreeSlot()
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: junkID, Quantity: totalJunk})
+ }
+
+ totalXP := scrapCount * xpPer
+ junkName := junkID
+ if def, err := g.ItemStore.Load(junkID); err == nil {
+ junkName = def.Name
+ }
+
+ msg := fmt.Sprintf("The altar hums. You identify %d scrap metal into %d %s.", scrapCount, totalJunk, junkName)
+
+ if newLevel := p.AddSkillXP(player.Scavenging, totalXP); newLevel > 0 {
+ sess.WriteLine(g.colorize(sess, "level_up",
+ fmt.Sprintf("*** You are now level %d scavenging! ***", newLevel)))
+ }
+
+ if p.OptionBool("xp_drops") && totalXP > 0 {
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp scv)", totalXP))
+ }
+ sess.WriteLine(msg)
+
+ g.AccountStore.SaveCharacter(p)
+ g.CancelAction(p)
+}
diff --git a/internal/game/action_state.go b/internal/game/action_state.go
index 1935703..0dc3d32 100644
--- a/internal/game/action_state.go
+++ b/internal/game/action_state.go
@@ -22,8 +22,9 @@ const (
ActionProducing ActionType = "producing"
ActionFletching ActionType = "fletching"
ActionEating ActionType = "eating"
- ActionCleaning ActionType = "cleaning"
- ActionMixing ActionType = "mixing"
+ ActionCleaning ActionType = "cleaning"
+ ActionMixing ActionType = "mixing"
+ ActionIdentifying ActionType = "identifying"
)
type ActionState struct {
@@ -79,6 +80,8 @@ func (a *ActionState) Description() string {
return "cleaning herbs"
case ActionMixing:
return "mixing " + a.TargetName
+ case ActionIdentifying:
+ return "identifying scrap at " + a.TargetName
}
return ""
}
diff --git a/internal/game/cmd_id.go b/internal/game/cmd_id.go
new file mode 100644
index 0000000..5ea146d
--- /dev/null
+++ b/internal/game/cmd_id.go
@@ -0,0 +1,144 @@
+package game
+
+import (
+ "fmt"
+
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+type altarConfig struct {
+ AltarID string
+ IdentifierID string
+ JunkID string
+ LevelReq int
+ XPPer int
+}
+
+var altarConfigs = []altarConfig{
+ {"solar_altar", "solar_identifier", "solarjunk", 1, 5},
+ {"hydro_altar", "hydro_identifier", "hydrojunk", 14, 8},
+ {"cosmic_altar", "cosmic_identifier", "cosmicjunk", 27, 10},
+ {"eco_altar", "eco_identifier", "ecojunk", 28, 12},
+ {"chaos_altar", "chaos_identifier", "chaosjunk", 35, 11},
+ {"bio_altar", "bio_identifier", "biojunk", 44, 16},
+ {"nature_altar", "nature_identifier", "naturejunk", 44, 16},
+ {"law_altar", "law_identifier", "lawjunk", 54, 18},
+ {"death_altar", "death_identifier", "deathjunk", 65, 22},
+ {"blood_altar", "blood_identifier", "bloodjunk", 77, 28},
+}
+
+type junkMultiplierEntry struct {
+ Level int
+ Multiplier int
+}
+
+var junkMultipliers = map[string][]junkMultiplierEntry{
+ "solarjunk": {
+ {1, 1}, {22, 2}, {44, 3}, {66, 4}, {88, 5}, {99, 6},
+ },
+ "hydrojunk": {
+ {14, 1}, {36, 2}, {66, 3}, {88, 4},
+ },
+ "cosmicjunk": {
+ {27, 1}, {56, 2}, {78, 3},
+ },
+ "ecojunk": {
+ {28, 1}, {56, 2}, {78, 3},
+ },
+ "chaosjunk": {
+ {35, 1}, {66, 2}, {88, 3},
+ },
+ "biojunk": {
+ {44, 1}, {78, 2}, {99, 3},
+ },
+ "naturejunk": {
+ {44, 1}, {78, 2}, {99, 3},
+ },
+ "lawjunk": {
+ {54, 1}, {88, 2},
+ },
+ "deathjunk": {
+ {65, 1}, {99, 2},
+ },
+ "bloodjunk": {
+ {77, 1}, {99, 2},
+ },
+}
+
+func junkMultiplier(junkID string, level int) int {
+ entries, ok := junkMultipliers[junkID]
+ if !ok {
+ return 1
+ }
+ mult := 1
+ for _, e := range entries {
+ if level >= e.Level {
+ mult = e.Multiplier
+ }
+ }
+ return mult
+}
+
+func (g *Game) doIdentify(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+ g.CancelAction(p)
+
+ var found altarConfig
+ foundAny := false
+ for _, ac := range altarConfigs {
+ if defID, _ := g.findStation(p.RoomID, []string{ac.AltarID}); defID != "" {
+ found = ac
+ foundAny = true
+ break
+ }
+ }
+ if !foundAny {
+ sess.WriteLine("There is no altar here.")
+ return
+ }
+
+ identifierName := found.IdentifierID
+ if def, err := g.ItemStore.Load(found.IdentifierID); err == nil {
+ identifierName = def.Name
+ }
+ if !p.HasItem(found.IdentifierID) {
+ sess.WriteLine(fmt.Sprintf("You need a %s to use this altar.", identifierName))
+ return
+ }
+
+ scrapCount := p.CountItem("scrap_metal")
+ if scrapCount == 0 {
+ sess.WriteLine("You don't have any scrap metal.")
+ return
+ }
+
+ scavLevel := p.Level(player.Scavenging)
+ if scavLevel < found.LevelReq {
+ junkName := found.JunkID
+ if def, err := g.ItemStore.Load(found.JunkID); err == nil {
+ junkName = def.Name
+ }
+ sess.WriteLine(fmt.Sprintf("You need level %d scavenging to identify %s.", found.LevelReq, junkName))
+ return
+ }
+
+ altarName := found.AltarID
+ if def, err := g.ObjectStore.Load(found.AltarID); err == nil {
+ altarName = def.Name
+ }
+
+ p.Action = &action.Action{
+ Type: "identify",
+ TargetID: found.AltarID,
+ TargetName: altarName,
+ WaitLeft: 1,
+ Data: map[string]any{
+ "junk_id": found.JunkID,
+ "xp_per": found.XPPer,
+ "level_req": found.LevelReq,
+ },
+ }
+ p.ActionState = &ActionState{Type: ActionIdentifying, TargetName: altarName}
+}
diff --git a/internal/game/game.go b/internal/game/game.go
index 15236f4..dff78d5 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -149,7 +149,8 @@ func classifyCommand(cmd string) CommandClass {
"attack", "kill",
"north", "n", "south", "s", "east", "e",
"west", "w", "up", "u", "down", "d",
- "quit", "use", "burn", "stoke", "search", "walk", "cook", "smelt", "smith", "craft", "mix":
+ "quit", "use", "burn", "stoke", "search", "walk", "cook", "smelt", "smith", "craft", "mix",
+ "id", "identify":
return ClassActive
case "eat", "fletch", "clean":
return ClassFree
@@ -397,6 +398,9 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
case "mix":
g.doMix(sess, strings.Join(args, " "))
return
+ case "id", "identify":
+ g.doIdentify(sess, strings.Join(args, " "))
+ return
case "talk", "speak", "ask":
g.CancelAction(p)
if len(args) == 0 {