Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion _datafiles/guides/building/scripting/FUNCTIONS_ACTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ ActorObjects are the basic object that represents Users and NPCs
- [ActorObject.TimerExpired(name string) bool](#actorobjecttimerexpiredname-string-bool)
- [ActorObject.TimerExists(name string) bool](#actorobjecttimerexistsname-string-bool)
- [ActorObject.AddEventLog(category string, message string)](#actorobjectaddeventlogcategory-string-message-string)
- [ActorObject.MarkVisitedRoom(roomId1 int [, roomId2, ...])](#actorobjectmarkvisitedroomroomid1-int--roomid2-)
- [ActorObject.MarkVisitedZone(zoneName string)](#actorobjectmarkvisitedzonezoneename-string)



Expand Down Expand Up @@ -638,4 +640,30 @@ Adds a line to the users Event Log (`history`)
| Argument | Explanation |
| --- | --- |
| category | A short single word category |
| message | A single line describing the event |
| message | A single line describing the event |

## [ActorObject.MarkVisitedRoom(roomId1 int [, roomId2, ...])](/internal/scripting/actor_func.go)
Marks one or more rooms as visited for this actor. Only applies to user actors; mobs do not track room visits. Each room is recorded under the zone it belongs to.

_Note: Non-positive room IDs are silently ignored._

| Argument | Explanation |
| --- | --- |
| roomId1, roomId2, ... | One or more room IDs to mark as visited |

**Example:**
```javascript
user.MarkVisitedRoom(101, 102, 103);
```

## [ActorObject.MarkVisitedZone(zoneName string)](/internal/scripting/actor_func.go)
Marks every room in the named zone as visited for this actor. Only applies to user actors; mobs do not track room visits. Accepts partial zone name matches.

| Argument | Explanation |
| --- | --- |
| zoneName | The zone name (or partial name) to mark all rooms visited in |

**Example:**
```javascript
user.MarkVisitedZone("frostfang");
```
28 changes: 27 additions & 1 deletion _datafiles/guides/building/scripting/FUNCTIONS_PARTY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ PartyObjects represent collections of actors (users and NPCs) that are grouped t
- [PartyObject.GiveExtraLife()](#partyobjectgiveextralife)
- [PartyObject.GrantXP(xpAmt int, reason string)](#partyobjectgrantxpxpamt-int-reason-string)
- [PartyObject.TimerSet(name string, period string)](#partyobjecttimersetname-string-period-string)
- [PartyObject.MarkVisitedRoom(roomId1 int [, roomId2, ...])](#partyobjectmarkvisitedroomroomid1-int--roomid2-)
- [PartyObject.MarkVisitedZone(zoneName string)](#partyobjectmarkvisitedzonezoneename-string)



Expand Down Expand Up @@ -195,4 +197,28 @@ Starts a new Round timer for all party members.
| Argument | Explanation |
| --- | --- |
| name | A string identifier. Reusing names will overwrite previously assigned names |
| period | How long until the timer expires. `1 real hour`, `1 hour`, etc |
| period | How long until the timer expires. `1 real hour`, `1 hour`, etc |

## [PartyObject.MarkVisitedRoom(roomId1 int [, roomId2, ...])](/internal/scripting/party_func.go)
Marks one or more rooms as visited for all user members of the party. Mobs in the party are unaffected.

| Argument | Explanation |
| --- | --- |
| roomId1, roomId2, ... | One or more room IDs to mark as visited |

**Example:**
```javascript
user.GetParty().MarkVisitedRoom(101, 102, 103);
```

## [PartyObject.MarkVisitedZone(zoneName string)](/internal/scripting/party_func.go)
Marks every room in the named zone as visited for all user members of the party. Mobs in the party are unaffected. Accepts partial zone name matches.

| Argument | Explanation |
| --- | --- |
| zoneName | The zone name (or partial name) to mark all rooms visited in |

**Example:**
```javascript
user.GetParty().MarkVisitedZone("frostfang");
```
3 changes: 3 additions & 0 deletions _datafiles/world/default/keywords.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ help:
- races
- who
- history
- visited
items:
- drop
- drink
Expand Down Expand Up @@ -125,6 +126,7 @@ help:
- buff
- build
- command
- copyover
- deafen
- item
- grant
Expand All @@ -145,6 +147,7 @@ help:
- syslogs
- zap
- zone
- visit
# Aliases for keywords when typing: help <keyword>
# Key is the target keyword, value is the list of aliases
help-aliases:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
The <ansi fg="command">visit</ansi> command manages room visit tracking for players:

<ansi fg="command">visit list</ansi>
List visit progress across all zones for yourself.

<ansi fg="command">visit list [username]</ansi>
List visit progress across all zones for the specified user.

<ansi fg="command">visit set [zonename|all]</ansi>
Mark all rooms in the specified zone as visited for yourself.
Use <ansi fg="command">all</ansi> to mark every zone.

<ansi fg="command">visit set [zonename|all] [username]</ansi>
Mark all rooms in the specified zone as visited for the target user.

<ansi fg="command">visit unset [zonename|all]</ansi>
Reset all rooms in the specified zone to unvisited for yourself.
Use <ansi fg="command">all</ansi> to reset every zone.

<ansi fg="command">visit unset [zonename|all] [username]</ansi>
Reset all rooms in the specified zone to unvisited for the target user.

Zone names support partial matching. The username can be an online character
name or an offline account username.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
┌─ <ansi fg="black-bold">.:</ansi><ansi fg="20">Zones Discovered</ansi> ─────────────────────────────────────────────────────┐
{{ $nlLen := sub (len .Records) 1 }}{{ range $idx, $zInfo := .Records }} <ansi fg="yellow-bold">{{ padRight 30 $zInfo.Name }}</ansi> <ansi fg="green">{{ $zInfo.BarFull }}</ansi><ansi fg="black-bold">{{ $zInfo.BarEmpty }}</ansi> <ansi fg="cyan-bold">{{ padRight 4 $zInfo.Completion }}</ansi>{{ if lt $idx $nlLen }}{{ end }}
{{ end -}}
└──────────────────────────────────────────────────────────────────────────┘
7 changes: 7 additions & 0 deletions _datafiles/world/default/templates/help/visited.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Help for ~visited~

The ~visited~ command shows all zones you have explored, along with the percentage of rooms visited in each.

## Usage:

~visited~
2 changes: 1 addition & 1 deletion _datafiles/world/default/templates/maps/map.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{{- $leftBorder := .LeftBorder -}}
{{- $midBorder := .MidBorder -}}
{{- $rightBorder := .RightBorder -}}
<ansi fg="black-bold">{{ $leftBorder.Top }}</ansi><ansi fg="black-bold"> .:</ansi><ansi fg="20">{{ printf ( printf "%%-%ds" ( sub $mapWidth 4 ) ) .Title }}</ansi><ansi fg="black-bold">{{ $rightBorder.Top }}</ansi>
<ansi fg="black-bold">{{ $leftBorder.Top }}</ansi><ansi fg="black-bold"> .:</ansi><ansi fg="20">{{ printf ( printf "%%-%ds" ( sub $mapWidth 4 ) ) ( printf "%s (%d%%)" .Title .ZoneCompletePct ) }}</ansi><ansi fg="black-bold">{{ $rightBorder.Top }}</ansi>
<ansi fg="black-bold">{{ index $leftBorder.Mid 0 }}{{ padRightX "" $midBorder.Top $mapWidth }}{{ index $rightBorder.Mid 0 }}</ansi>
{{ range $index, $line := .DisplayLines }}
{{- $mod := mod $index 2 -}}
Expand Down
27 changes: 22 additions & 5 deletions _datafiles/world/default/users/1.yaml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ character:
other adventurers, granting them strength, wisdom, or fortune beyond their wildest
dreams. Conversely, he can also mete out justice to those who would seek to disrupt
the balance of the world, swiftly and decisively.
roomid: 487
roomid: 1
roomidonreset: 0
zone: Frostfang Slums
zone: Frostfang
raceid: 2
stats:
strength:
Expand Down Expand Up @@ -64,15 +64,15 @@ character:
list:
- buffid: 28
permabuff: true
roundcounter: 22
roundcounter: 9
triggersleft: 1000000000
- buffid: 29
permabuff: true
roundcounter: 15
roundcounter: 32
triggersleft: 1000000000
- buffid: 39
permabuff: true
roundcounter: 4
roundcounter: 6
triggersleft: 1000000000
equipment:
weapon:
Expand Down Expand Up @@ -147,6 +147,23 @@ character:
- itemid: 30002
uses: 1
created: 2024-10-31T13:28:45.391415-07:00
zonesvisited:
Frostfang:
"0": "0xFFFFFFFEFFFFFFFE"
"1": "0x00000000000017FF"
"2": "0x000000C000000000"
"4": "0x0002EFFFFFDFFFFC"
"6": "0x0003000000000000"
"9": "0x0000000800000000"
"10": "0x0000000000000004"
"11": "0x0000000018000000"
"12": "0x2DFFFFF8FFBFFF00"
"13": "0x0000800000000000"
"15": "0x00000C0000000000"
Frostfang Slums:
"6": "0x0080000000000000"
Whispering Wastes:
"2": "0x00000F0000000000"
itemstorage:
items:
- itemid: 20011
Expand Down
3 changes: 3 additions & 0 deletions _datafiles/world/empty/keywords.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ help:
- races
- who
- history
- visited
items:
- drop
- drink
Expand Down Expand Up @@ -125,6 +126,7 @@ help:
- buff
- build
- command
- copyover
- deafen
- item
- grant
Expand All @@ -145,6 +147,7 @@ help:
- syslogs
- zap
- zone
- visit
# Aliases for keywords when typing: help <keyword>
# Key is the target keyword, value is the list of aliases
help-aliases:
Expand Down
65 changes: 65 additions & 0 deletions internal/characters/character.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ type Character struct {
Pet pets.Pet `yaml:"pet,omitempty"` // Do they have a pet?
Created time.Time `yaml:"created"` // When this character was created
Timers map[string]gametime.RoundTimer `yaml:"timers,omitempty"` // any special timers added to this character
ZonesVisited map[string]RoomBitset `yaml:"zonesvisited,omitempty"` // permanent record of every room visited, keyed by zone name
roomHistory []int // A stack FILO of the last X rooms the character has been in
PlayerDamage map[int]int `yaml:"-"` // key = who, value = how much
LastPlayerDamage uint64 `yaml:"-"` // last round a player damaged this character
Expand Down Expand Up @@ -984,6 +985,70 @@ func (c *Character) RememberRoom(roomId int) {
c.roomHistory = append(c.roomHistory, roomId)
}

// MarkVisitedRoom permanently records that this character has visited roomId
// in the given zone. Safe to call every time a player enters a room.
// Returns true only if this specific call completed the zone (i.e. every room
// in validRoomIds is now visited). Returns false if the room was already
// visited, if validRoomIds is empty, or if the zone is still incomplete.
func (c *Character) MarkVisitedRoom(roomId int, zone string, validRoomIds map[int]struct{}) bool {
if c.ZonesVisited == nil {
c.ZonesVisited = make(map[string]RoomBitset)
}
if _, ok := c.ZonesVisited[zone]; !ok {
c.ZonesVisited[zone] = make(RoomBitset)
}

// If the bit was already set this call cannot be the completing visit.
if c.ZonesVisited[zone].Has(roomId) {
return false
}

c.ZonesVisited[zone].Set(roomId)

if len(validRoomIds) == 0 {
return false
}

return c.ZonesVisited[zone].IsComplete(validRoomIds)
}

// HasVisitedRoom reports whether this character has ever visited roomId in zone.
func (c *Character) HasVisitedRoom(roomId int, zone string) bool {
if c.ZonesVisited == nil {
return false
}
bs, ok := c.ZonesVisited[zone]
if !ok {
return false
}
return bs.Has(roomId)
}

// ZoneVisitProgress returns how many rooms the character has visited in zone
// and the total number of rooms in that zone, allowing callers to compute a
// completion percentage. validRoomIds should come from ZoneConfig.RoomIds.
func (c *Character) ZoneVisitProgress(zone string, validRoomIds map[int]struct{}) (visited int, total int) {
total = len(validRoomIds)
if c.ZonesVisited == nil {
return 0, total
}
bs, ok := c.ZonesVisited[zone]
if !ok {
return 0, total
}
return bs.CountIn(validRoomIds), total
}

// ZoneVisitPercent returns the percentage (0–100) of rooms in zone that the
// character has visited. Returns 0 when the zone has no rooms.
func (c *Character) ZoneVisitPercent(zone string, validRoomIds map[int]struct{}) int {
visited, total := c.ZoneVisitProgress(zone, validRoomIds)
if total == 0 {
return 0
}
return int(float64(visited) / float64(total) * 100)
}

func (c *Character) IsQuestDone(questToken string) bool {
testQuestId, _ := quests.TokenToParts(questToken)
if c.QuestProgress == nil {
Expand Down
15 changes: 14 additions & 1 deletion internal/characters/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ The `internal/characters` package is the core character system for GoMud, handli
- **Experience and leveling**: Level progression and TNL (To Next Level) calculations
- **Persistence**: Character data serialization/deserialization

### Room Visit Tracking (`roombitset.go`)
- **RoomBitset**: Chunked bitset type (`map[uint16]uint64`) for memory-efficient permanent room visit tracking
- **Block-based storage**: Each map key is `roomId/64`; each value is a `uint64` bitmask covering that 64-room window
- **Zone-sharded on Character**: `ZonesVisited map[string]RoomBitset` persisted to YAML under `zonesvisited`
- **Human-readable serialization**: Blocks serialize as hex strings (e.g. `"0x000000000000003F"`) for debuggable save files
- **Pruning**: `RoomBitset.Prune(validRoomIds)` clears bits for deleted rooms and removes empty blocks

### Character Statistics System
- **Six core stats**: Strength, Speed, Smarts, Vitality, Mysticism, Perception
- **Stat scaling**: Stats over 100 use `SQRT(overage)*2` formula for diminishing returns
Expand Down Expand Up @@ -46,7 +53,8 @@ The `internal/characters` package is the core character system for GoMud, handli
- YAML-based character data storage
- Automatic saving with configurable intervals
- Character creation timestamps and history tracking
- Room history for movement tracking
- Short-term room history for map rendering (`roomHistory`, capped by memory capacity)
- Permanent room visit tracking via `ZonesVisited` (chunked bitset, persisted to YAML)

### Dynamic Stat System
- Base stats from race definitions
Expand Down Expand Up @@ -91,6 +99,8 @@ The `internal/characters` package is the core character system for GoMud, handli
- Equipment management through worn item slots
- State management through adjectives and flags
- Combat integration through aggro and damage tracking
- Room visit tracking via `MarkVisitedRoom(roomId, zone)` and queried with `HasVisitedRoom(roomId, zone)`
- Zone exploration progress via `ZoneVisitProgress(zone, validRoomIds)` returning `(visited, total int)`

## Testing
Comprehensive test coverage in `*_test.go` files covering:
Expand All @@ -101,5 +111,8 @@ Comprehensive test coverage in `*_test.go` files covering:
- Shop mechanics and restocking
- Kill/death tracking
- Cooldown management
- `RoomBitset` set/has/count/prune operations
- `RoomBitset` YAML round-trip serialization
- `MarkVisitedRoom`, `HasVisitedRoom`, and `ZoneVisitProgress` integration

This package serves as the foundation for all character-related functionality in GoMud, providing a rich and flexible character model that supports both player and NPC needs.
Loading
Loading