aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--TODO.md72
-rw-r--r--data/behaviors/chop_tree.yaml1
-rw-r--r--data/behaviors/fish_net.yaml1
-rw-r--r--data/behaviors/mine_copper.yaml1
-rw-r--r--internal/action/behavior.go1
-rw-r--r--internal/game/action_default_target.go79
-rw-r--r--internal/game/action_gather.go8
-rw-r--r--internal/game/game.go19
9 files changed, 116 insertions, 82 deletions
diff --git a/README.md b/README.md
index 102eed4..98687a6 100644
--- a/README.md
+++ b/README.md
@@ -6,15 +6,15 @@ There is not much here yet.
# Gameplay
-- Type and wait skilling
-- Attack and wait combat
+- Type and wait skilling.
+- Attack and wait combat.
- Semi-AFK and active gameplay styles.
-- 600ms game ticks
-- 28 slot inventory
-- Classless. All attributes are skills that automatically level 1-99 as you use them
+- 600ms game ticks.
+- 28 slot inventory.
+- Classless. All attributes are skills that automatically level 1-99 as you use them.
- 100% solo friendly just like you remember from 2001. Grind and chat with friends.
-- Ironman-style, slow, grind up your own gear progression. Vendors don't sell craftables like armor.
-- A detailed casino for you to waste all the credits you grind up for
+- Ironman-style, slow, grind up your own gear progression. Vendors don't sell craftable items.
+- A detailed casino for you to waste all the credits you grind up for.
## Skills
@@ -42,7 +42,7 @@ There is not much here yet.
| Fletching | Create cheap low-tech bows, crossbows, darts, arrows, and bolts. |
| Alchemy | Clean herbs and brew potions for temporary buffs. |
| Construction | Build a player-owned house for fast travel or to show off rare items. |
-| Agility | Higher agility means shorter wait times when AFK travelling with APS. |
+| Agility | Get more stamina and shorter wait times when AFK travelling with APS. |
# Building
diff --git a/TODO.md b/TODO.md
index a139835..52a4d54 100644
--- a/TODO.md
+++ b/TODO.md
@@ -4,7 +4,7 @@ A low-tech sci-fi MUD set on a terraformed asteroid. Runescape-style combat and
## Theme
-Not generic fantasy. Takes place on a terraformed asteroid. Technology has regressed — people scrap and scavenge remnants of the old world. "Science" replaces Prayer, "Technology" replaces Magic. Scavenging replaces Runecrafting.
+Takes place on a terraformed asteroid. Technology has regressed — people scrap and scavenge remnants of the old world. "Science" replaces Prayer, "Technology" replaces Magic. Scavenging replaces Runecrafting.
## Skills (23 total, all go from 1-99)
@@ -193,75 +193,7 @@ See WORLDBUILDING.md for full examples of every data type with explanations.
## Action Plan
-### Phase 1: Tiny ASCII Map
-
-- [x] Limit the room description to 70 characters wide. Add a setting named "maxWidth" in an appropriate section of config.yaml along with comment explaining it.
-
-Next to the block of room description text should be an ASCII mini-map showing the current room as @ and a grid of the rooms and connections surrounding it. A full-size ASCII map for the "map" command will eventually be implemented so keep that in mind for methodology and potential functions we can reuse.
-Each line of the map should print on the maxWidth+2 character after the description (so there's always at least one space between the description and map)
-If the map is longer than the description, just keep printing new lines for the map.
-
-The first line is always ╔═════╗
-The last line is always ╚═════╝
-Each of the 5 inner lines begins and ends with ║
-
-The map shows up to 9 rooms and any exits connecting them. The 5 inner lines have 7 characters each (including the border) which be defined like this:
-
-Line 1: [║, Room A (shown if there is a west exit from North Room or north exit from West Room), — if there is a west exit from North Room, North Room, — if there is an east exit from North Room, Room B (shown if there is an east exit from North Room or north exit from East Room), ║]
-Line 2: [║, | if there is a north exit from West Room, Blank, | if there is a north exit from Current Room, ↑ if there is an up exit from Current Room, | if there is a north exit from East Room, ║]
-Line 3: [║, West Room, — if there is a west exit from Current Room, @, — if there is an east exit from Current Room, East Room, ║]
-Line 4: [║, | if there is a south exit from West Room, ↓ if there is a down exit from Current Room, | if there is a south exit from Current Room, Blank, | if there is a south exit from East Room, ║]
-Line 5: [║, Room C (shown if there is a west exit from South Room or south exit from West Room), — if there is a west exit from South Room, South Room, — if there is an east exit from South Room, Room D (shown if there is an east exit from South Room or south exit from East Room), ║]
-
-Note that North Room, East Room, South Room, and West Room can still show up even if there isn't a direct link from the current room to them. For example if there is a to East Room, then that has an exit north, then that room has an exit west, the 'north room' still shows up on the map.
-
-Each room has a parameter map_symbol to identify it on the map.
-
-This is how the map looks when every possible room and exit is shown. o is the map_symbol for each room in this example:
-
-╔═════╗
-║o—o—o║
-║| |↑|║
-║o—@—o║
-║|↓| |║
-║o—o—o║
-╚═════╝
-
-This is a map with fewer connections. Note that 'Room A' and 'North Room' show up despite only being connected through East Room and Room B.
-
-╔═════╗
-║o—o—o║
-║ |║
-║o—@—o║
-║| ║
-║o ║
-╚═════╝
-
-
-Here's how the room description would look limited to 70 characters long with the map to the right:
-
-Central Market
-The market square pulses with the unbridled energy of a thousand ╔═════╗
-voices, a symphony of commerce and humanity that hasn't changed in ║o—o—o║
-generations. Overhead, September clouds drift lazily across the sky, ║| |↑|║
-occasionally releasing weak sunlight that transforms the ancient ║o—@—o║
-stone beneath countless feet into patches of burnished gold. ║|↓| |║
- ║o—o—o║
- ╚═════╝
-
-- [x] This is a feature that can be toggled with the "tinymap" toggle. If the toggle is off, don't print the map at all.
-- [x] Add a toggle "left-tinymap". If this is enabled, the map should be printed on each line before the room description. For example:
-
-Central Market
-╔═════╗ The market square pulses with the unbridled energy of a thousand
-║o—o—o║ voices, a symphony of commerce and humanity that hasn't changed in
-║| |↑|║ generations. Overhead, September clouds drift lazily across the sky,
-║o—@—o║ occasionally releasing weak sunlight that transforms the ancient
-║|↓| |║ stone beneath countless feet into patches of burnished gold.
-║o—o—o║
-╚═════╝
-
-### Phase 1b: Map & Option System
+### Phase 1: ASCII map implemented with BFS
- [x] Replaced `toggle` command with `option`/`options` — supports bool (on/off), string (choice lists), and int option types
- [x] All old toggles migrated to the option system (tinymap, left-tinymap, xpdrops, exits, mobenter, mobleave, mobspawn, reserve, depletion, description)
diff --git a/data/behaviors/chop_tree.yaml b/data/behaviors/chop_tree.yaml
index 445047d..9072e33 100644
--- a/data/behaviors/chop_tree.yaml
+++ b/data/behaviors/chop_tree.yaml
@@ -10,6 +10,7 @@ success:
per_level: 0.01
cap: 0.95
gather_message: "You swing your axe at the tree..."
+depleted_message: "You don't see any usable wood in this tree."
fail_message: "You swing but get no logs."
drops:
- item_id: logs
diff --git a/data/behaviors/fish_net.yaml b/data/behaviors/fish_net.yaml
index b971710..b5398aa 100644
--- a/data/behaviors/fish_net.yaml
+++ b/data/behaviors/fish_net.yaml
@@ -9,6 +9,7 @@ success:
per_level: 0.005
cap: 0.90
gather_message: "You cast your net into the water..."
+depleted_message: "You don't see any fish swimming here."
fail_message: "You catch nothing."
drops:
- item_id: raw_shrimps
diff --git a/data/behaviors/mine_copper.yaml b/data/behaviors/mine_copper.yaml
index d4b8fbf..b4f27b9 100644
--- a/data/behaviors/mine_copper.yaml
+++ b/data/behaviors/mine_copper.yaml
@@ -10,6 +10,7 @@ success:
per_level: 0.01
cap: 0.95
gather_message: "You swing your pickaxe at the rock..."
+depleted_message: "You don't see any ore in this rock."
fail_message: "You chip away but get nothing useful."
drops:
- item_id: copper_ore
diff --git a/internal/action/behavior.go b/internal/action/behavior.go
index a3c8802..4ffd7e5 100644
--- a/internal/action/behavior.go
+++ b/internal/action/behavior.go
@@ -9,6 +9,7 @@ type GatherConfig struct {
Bait string `yaml:"bait"`
Success SuccessFormula `yaml:"success"`
GatherMsg string `yaml:"gather_message"`
+ DepletedMessage string `yaml:"depleted_message"`
FailMsg string `yaml:"fail_message"`
Drops []DropEntry `yaml:"drops"`
DepleteDelay int `yaml:"deplete_delay"`
diff --git a/internal/game/action_default_target.go b/internal/game/action_default_target.go
new file mode 100644
index 0000000..45060cc
--- /dev/null
+++ b/internal/game/action_default_target.go
@@ -0,0 +1,79 @@
+package game
+
+import (
+ "strings"
+)
+
+func verbToSkill(verb string) string {
+ switch verb {
+ case "mine":
+ return "mining"
+ case "chop", "cut":
+ return "woodcutting"
+ case "fish":
+ return "fishing"
+ }
+ return ""
+}
+
+func (g *Game) resolveDefaultTarget(roomID int, verb string) string {
+ skill := verbToSkill(verb)
+ if skill == "" {
+ return ""
+ }
+
+ objStates := g.World.AllObjInstances(roomID)
+
+ type candidate struct {
+ defID string
+ name string
+ }
+ var candidates []candidate
+ seen := make(map[string]bool)
+
+ for _, st := range objStates {
+ if seen[st.DefID] {
+ continue
+ }
+ seen[st.DefID] = true
+
+ objDef, err := g.ObjectStore.Load(st.DefID)
+ if err != nil || objDef.BehaviorID == "" {
+ continue
+ }
+
+ bh, err := g.BehaviorStore.Load(objDef.BehaviorID)
+ if err != nil || bh.Type != "gather" {
+ continue
+ }
+
+ cfg, err := g.BehaviorStore.LoadGather(objDef.BehaviorID)
+ if err != nil {
+ continue
+ }
+
+ if strings.EqualFold(cfg.Skill, skill) {
+ candidates = append(candidates, candidate{defID: st.DefID, name: objDef.Name})
+ }
+ }
+
+ if len(candidates) == 1 {
+ return candidates[0].name
+ }
+ return ""
+}
+
+func (g *Game) resolveDefaultMob(roomID int) string {
+ mobs := g.MobStore.MobsInRoom(roomID)
+ if len(mobs) == 0 {
+ return ""
+ }
+
+ firstDef := mobs[0].DefID
+ for _, m := range mobs[1:] {
+ if m.DefID != firstDef {
+ return ""
+ }
+ }
+ return mobs[0].Name
+}
diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go
index 62637c3..2ca2e82 100644
--- a/internal/game/action_gather.go
+++ b/internal/game/action_gather.go
@@ -26,7 +26,13 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
}
if st.Depleted {
- if cfg.SharedDeplete > 0 {
+ if cfg.DepletedMessage != "" {
+ msg := cfg.DepletedMessage
+ if p.OptionBool("depletion") {
+ msg += fmt.Sprintf(" (%d ticks to respawn)", st.DepleteTimer)
+ }
+ sess.WriteLine(msg)
+ } else if cfg.SharedDeplete > 0 {
sess.WriteLine(fmt.Sprintf("The %s has been cut down for %d more ticks.", obj.Name, st.DepleteTimer))
} else {
sess.WriteLine(fmt.Sprintf("The %s is depleted for %d more ticks.", obj.Name, st.DepleteTimer))
diff --git a/internal/game/game.go b/internal/game/game.go
index 8151877..cc187a7 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -100,7 +100,8 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
return
}
- if p, ok := sess.Player.(*player.Player); ok {
+ p, _ := sess.Player.(*player.Player)
+ if p != nil {
g.cancelRest(p.Name)
}
@@ -153,7 +154,13 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
}
case "attack", "kill":
if len(args) == 0 {
- sess.WriteLine("Attack what?")
+ target := g.resolveDefaultMob(p.RoomID)
+ if target == "" {
+ sess.WriteLine("Attack what?")
+ break
+ }
+ g.doAttack(sess, target)
+ return
} else {
g.doAttack(sess, strings.Join(args, " "))
return
@@ -209,7 +216,13 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
}
case "mine", "chop", "fish", "cut", "use", "pull", "push":
if len(args) == 0 {
- sess.WriteLine(fmt.Sprintf("%s what?", cmd))
+ target := g.resolveDefaultTarget(p.RoomID, cmd)
+ if target == "" {
+ sess.WriteLine(fmt.Sprintf("%s what?", cmd))
+ break
+ }
+ g.StartAction(sess, cmd, target)
+ return
} else {
g.StartAction(sess, cmd, strings.Join(args, " "))
return