diff --git a/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md b/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md index 3b87d3fcc..89a9f4225 100644 --- a/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md +++ b/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md @@ -21,6 +21,8 @@ take effect immediately and are persisted the next time the character is saved. - [PetObject.GetCapacity() int](#petobjectgetcapacity-int) - [PetObject.ItemCount() int](#petobjectitemcount-int) - [PetObject.HasScript() bool](#petobjecthasscript-bool) + - [PetObject.IsMissing() bool](#petobjectismissing-bool) + - [PetObject.GoMissing(rounds int)](#petobjectgomissingrounds-int) --- @@ -200,3 +202,54 @@ Returns `true` if this pet type has a script file on disk. Useful in generic scripts that want to check whether a pet will respond to events before attempting to trigger them. + +--- + +## [PetObject.IsMissing() bool](/internal/scripting/pet_func.go) +Returns `true` when the pet is temporarily absent (`MissingCountdown > 0`). + +While missing, the pet does not appear in room descriptions, does not +participate in combat, does not contribute stat or buff bonuses, and does not +respond to commands or `PetAct` ticks. + +**Example:** +```javascript +var pet = actor.GetPet(); +if (pet !== null && pet.IsMissing()) { + actor.SendText('Your pet has wandered off somewhere...'); +} +``` + +--- + +## [PetObject.GoMissing(rounds int)](/internal/scripting/pet_func.go) +Causes the pet to go absent for the given number of rounds, or returns it +immediately when called with `0`. + +- **Positive value**: sets the countdown, fires `PetLeave()`, and hides the + pet from all game systems until the countdown reaches zero. +- **Zero**: clears the countdown immediately and fires `PetReturn()`. Has no + effect if the pet is not currently missing. + +| Argument | Explanation | +| --- | --- | +| rounds | Rounds to be absent, or `0` to return immediately. Negative values are treated as `0`. | + +**Example:** +```javascript +function PetAct(pet, actor, room) { + // 1% chance per round for the pet to wander off for 10 rounds + if (RandInt(1, 100) === 1) { + pet.GoMissing(10); + } +} + +// Return the pet early from a script command +function onCommand_recall(rest, pet, actor, room) { + if (pet.IsMissing()) { + pet.GoMissing(0); + actor.SendText('Your pet bounds back to your side!'); + } + return true; +} +``` diff --git a/_datafiles/guides/building/scripting/SCRIPTING_PETS.md b/_datafiles/guides/building/scripting/SCRIPTING_PETS.md index a880250d2..d9b01ae8e 100644 --- a/_datafiles/guides/building/scripting/SCRIPTING_PETS.md +++ b/_datafiles/guides/building/scripting/SCRIPTING_PETS.md @@ -133,3 +133,60 @@ function onCommand_pet(rest, pet, actor, room) { return false; } ``` + +--- + +```javascript +function PetLeave(pet, actor, room) { +} +``` + +`PetLeave()` is called immediately when +[PetObject.GoMissing()](FUNCTIONS_PETS.md#petobjectgomissingrounds-int) is +invoked. Use it to emit a message or trigger effects when the pet disappears. + +The pet is already marked as missing when this fires, so `pet.IsMissing()` +returns `true` inside the handler. + +There is no return value. + +| Argument | Explanation | +| --- | --- | +| pet | [PetObject](FUNCTIONS_PETS.md) — the pet. | +| actor | [ActorObject](FUNCTIONS_ACTORS.md) — the player who owns the pet. | +| room | [RoomObject](FUNCTIONS_ROOMS.md) — the room both are in. | + +**Example:** +```javascript +function PetLeave(pet, actor, room) { + room.SendText(pet.NameSimple() + ' darts into the shadows and disappears!'); +} +``` + +--- + +```javascript +function PetReturn(pet, actor, room) { +} +``` + +`PetReturn()` is called the round the pet's `MissingCountdown` reaches zero. +Use it to announce the pet's return or apply any effects. + +The countdown has already reached zero when this fires, so `pet.IsMissing()` +returns `false` inside the handler. + +There is no return value. + +| Argument | Explanation | +| --- | --- | +| pet | [PetObject](FUNCTIONS_PETS.md) — the pet. | +| actor | [ActorObject](FUNCTIONS_ACTORS.md) — the player who owns the pet. | +| room | [RoomObject](FUNCTIONS_ROOMS.md) — the room both are in. | + +**Example:** +```javascript +function PetReturn(pet, actor, room) { + room.SendText(pet.NameSimple() + ' trots back to your side.'); +} +``` diff --git a/internal/characters/character.go b/internal/characters/character.go index 82df32e71..074c21cbf 100644 --- a/internal/characters/character.go +++ b/internal/characters/character.go @@ -1442,7 +1442,11 @@ func (c *Character) MovementCost() int { } func (c *Character) StatMod(statName string) int { - return c.Equipment.StatMod(statName) + c.Buffs.StatMod(statName) + c.Pet.StatMod(statName) + petMod := 0 + if !c.Pet.IsMissing() { + petMod = c.Pet.StatMod(statName) + } + return c.Equipment.StatMod(statName) + c.Buffs.StatMod(statName) + petMod } // returns true if something has changed. @@ -2262,7 +2266,7 @@ func (c *Character) reapplyPermabuffs(removedItems ...items.Item) { } // Apply any buffs from pet - if c.Pet.Exists() { + if c.Pet.Exists() && !c.Pet.IsMissing() { for _, buffId := range c.Pet.GetBuffs() { buffIdCount[buffId] = 100 // Don't allow pet buffs to be removed, keep this number high } diff --git a/internal/combat/combat.go b/internal/combat/combat.go index 7243063bf..551250bcb 100644 --- a/internal/combat/combat.go +++ b/internal/combat/combat.go @@ -405,52 +405,54 @@ func calculateCombat(sourceChar characters.Character, targetChar characters.Char } // Pet has a 20% chance per attack round to join the fight (once, regardless of weapon count) - chance, petDmg := sourceChar.Pet.GetEffectiveDamage() - if chance > 0 && util.RollDice(1, chance) <= chance { - if sourceChar.RoomId == targetChar.RoomId { - if sourceChar.Pet.Exists() && petDmg.DiceRoll != `` { + if sourceChar.Pet.Exists() && !sourceChar.Pet.IsMissing() { + chance, petDmg := sourceChar.Pet.GetEffectiveDamage() + if chance > 0 && util.RollDice(1, chance) <= chance { + if sourceChar.RoomId == targetChar.RoomId { + if petDmg.DiceRoll != `` { - pAttacks, pDCount, pDSides, pDBonus, critBuffs := sourceChar.Pet.GetDiceRoll() - combatMsgs := sourceChar.Pet.GetCombatMessages(string(targetType)) + pAttacks, pDCount, pDSides, pDBonus, critBuffs := sourceChar.Pet.GetDiceRoll() + combatMsgs := sourceChar.Pet.GetCombatMessages(string(targetType)) - for p := 0; p < pAttacks; p++ { + for p := 0; p < pAttacks; p++ { - if !Hits(0, targetChar.Stats.Speed.ValueAdj, 0) { - targetDisplayName := fmt.Sprintf(`%s`, string(targetType), targetChar.Name) - toAttackerMsg := combatMsgs.ApplyTokens(combatMsgs.Miss, sourceChar.Pet.DisplayName(), 0, targetDisplayName) - attackResult.SendToSource(toAttackerMsg) - continue - } + if !Hits(0, targetChar.Stats.Speed.ValueAdj, 0) { + targetDisplayName := fmt.Sprintf(`%s`, string(targetType), targetChar.Name) + toAttackerMsg := combatMsgs.ApplyTokens(combatMsgs.Miss, sourceChar.Pet.DisplayName(), 0, targetDisplayName) + attackResult.SendToSource(toAttackerMsg) + continue + } - attackTargetDamage := util.RollDice(pDCount, pDSides) + pDBonus + attackTargetDamage := util.RollDice(pDCount, pDSides) + pDBonus - attackTargetDamage, _ = applyDefenseReduction(attackTargetDamage, targetChar.GetDefense()) + attackTargetDamage, _ = applyDefenseReduction(attackTargetDamage, targetChar.GetDefense()) - attackResult.DamageToTarget += attackTargetDamage + attackResult.DamageToTarget += attackTargetDamage - targetDisplayName := fmt.Sprintf(`%s`, string(targetType), targetChar.Name) - petDisplayName := sourceChar.Pet.DisplayName() + targetDisplayName := fmt.Sprintf(`%s`, string(targetType), targetChar.Name) + petDisplayName := sourceChar.Pet.DisplayName() - toAttackerMsg := combatMsgs.ApplyTokens(combatMsgs.ToOwner, petDisplayName, attackTargetDamage, targetDisplayName) - attackResult.SendToSource(toAttackerMsg) + toAttackerMsg := combatMsgs.ApplyTokens(combatMsgs.ToOwner, petDisplayName, attackTargetDamage, targetDisplayName) + attackResult.SendToSource(toAttackerMsg) - toDefenderMsg := combatMsgs.ApplyTokens(combatMsgs.ToTarget, petDisplayName, attackTargetDamage, targetDisplayName) - attackResult.SendToTarget(toDefenderMsg) + toDefenderMsg := combatMsgs.ApplyTokens(combatMsgs.ToTarget, petDisplayName, attackTargetDamage, targetDisplayName) + attackResult.SendToTarget(toDefenderMsg) - toAttackerRoomMsg := combatMsgs.ApplyTokens(combatMsgs.ToRoom, petDisplayName, attackTargetDamage, targetDisplayName) - attackResult.SendToTargetRoom(toAttackerRoomMsg) + toAttackerRoomMsg := combatMsgs.ApplyTokens(combatMsgs.ToRoom, petDisplayName, attackTargetDamage, targetDisplayName) + attackResult.SendToTargetRoom(toAttackerRoomMsg) - // pets doing max damage are considered "crits" and will always apply any special critBuffs - if len(critBuffs) > 0 && (attackTargetDamage == (pDCount*pDSides)+pDBonus) { - attackResult.BuffTarget = critBuffs + // pets doing max damage are considered "crits" and will always apply any special critBuffs + if len(critBuffs) > 0 && (attackTargetDamage == (pDCount*pDSides)+pDBonus) { + attackResult.BuffTarget = critBuffs + } } - } + } } } } - } + return attackResult } diff --git a/internal/hooks/NewRound_UserRoundTick.go b/internal/hooks/NewRound_UserRoundTick.go index 5bfbdde00..0b63b4728 100644 --- a/internal/hooks/NewRound_UserRoundTick.go +++ b/internal/hooks/NewRound_UserRoundTick.go @@ -97,9 +97,18 @@ func UserRoundTick(e events.Event) events.ListenerReturn { user.Command(`zombieact`) } + // Decrement pet missing countdown each round. + petJustReturned := false + if user.Character.Pet.Exists() && user.Character.Pet.IsMissing() { + if user.Character.Pet.DecrementMissing() { + petJustReturned = true + scripting.TryPetScriptEvent(`PetReturn`, uId) + } + } + // Fire PetAct script using the pet type's configured RoundActChance. - // Not called while the owner is in combat. - if user.Character.Pet.Exists() && user.Character.Aggro == nil && user.Character.Pet.RoundActChance > 0 && util.Rand(100) < user.Character.Pet.RoundActChance { + // Not called while the owner is in combat, or on the same round the pet returns. + if !petJustReturned && user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() && user.Character.Aggro == nil && user.Character.Pet.RoundActChance > 0 && util.Rand(100) < user.Character.Pet.RoundActChance { scripting.TryPetScriptEvent(`PetAct`, uId) } diff --git a/internal/pets/pets.go b/internal/pets/pets.go index 7134c085a..c523217c7 100644 --- a/internal/pets/pets.go +++ b/internal/pets/pets.go @@ -19,16 +19,17 @@ import ( ) type Pet struct { - Name string `yaml:"name,omitempty"` // Name of the pet (player provided hopefully) - NameStyle string `yaml:"namestyle,omitempty"` // Optional color pattern to apply - Type string `yaml:"type"` // type of pet - RoundActChance int `yaml:"roundactchance,omitempty"` // 0-100 chance per round to fire PetAct script - Food Food `yaml:"food,omitempty"` // how much food the pet has - Level int `yaml:"level,omitempty"` // Pet level (1-10) - LastMealRound uint8 `yaml:"lastmealround,omitempty"` // When the pet was last fed - LastLevelCheck string `yaml:"lastlevelcheck,omitempty"` // "{year}.{day}" of last daily tick - Abilities []PetAbility `yaml:"abilities,omitempty"` // Refreshed from definition file on Validate() - Items []items.Item `yaml:"items,omitempty"` // Items held by this pet + Name string `yaml:"name,omitempty"` // Name of the pet (player provided hopefully) + NameStyle string `yaml:"namestyle,omitempty"` // Optional color pattern to apply + Type string `yaml:"type"` // type of pet + RoundActChance int `yaml:"roundactchance,omitempty"` // 0-100 chance per round to fire PetAct script + Food Food `yaml:"food,omitempty"` // how much food the pet has + Level int `yaml:"level,omitempty"` // Pet level (1-10) + LastMealRound uint8 `yaml:"lastmealround,omitempty"` // When the pet was last fed + LastLevelCheck string `yaml:"lastlevelcheck,omitempty"` // "{year}.{day}" of last daily tick + Abilities []PetAbility `yaml:"abilities,omitempty"` // Refreshed from definition file on Validate() + Items []items.Item `yaml:"items,omitempty"` // Items held by this pet + MissingCountdown int `yaml:"missingcountdown,omitempty"` // When non-zero, pet is absent cachedAbility *PetAbility `yaml:"-"` // cached current ability cachedLevel int `yaml:"-"` // level when cache was set @@ -86,6 +87,30 @@ func (p *Pet) Exists() bool { return p.Type != `` } +// IsMissing returns true when the pet is temporarily absent. +func (p *Pet) IsMissing() bool { + return p.MissingCountdown > 0 +} + +// GoMissing sets the missing countdown to the given number of rounds. +// A value of zero clears the missing state immediately. +func (p *Pet) GoMissing(rounds int) { + if rounds < 0 { + rounds = 0 + } + p.MissingCountdown = rounds +} + +// DecrementMissing decrements the missing countdown by one. +// Returns true if the countdown just reached zero (pet is returning this round). +func (p *Pet) DecrementMissing() bool { + if p.MissingCountdown <= 0 { + return false + } + p.MissingCountdown-- + return p.MissingCountdown == 0 +} + func (p *Pet) DisplayName() string { name := p.Name diff --git a/internal/rooms/roomdetails.go b/internal/rooms/roomdetails.go index de37f0d4a..07fa6122e 100644 --- a/internal/rooms/roomdetails.go +++ b/internal/rooms/roomdetails.go @@ -264,7 +264,7 @@ func GetDetails(r *Room, user *users.UserRecord, tinymap ...[]string) RoomTempla } } - if user.Character.Pet.Exists() && r.RoomId == user.Character.RoomId { + if user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() && r.RoomId == user.Character.RoomId { details.VisiblePlayers = append(details.VisiblePlayers, fmt.Sprintf(`%s (your pet)`, user.Character.Pet.DisplayName())) } diff --git a/internal/rooms/rooms.go b/internal/rooms/rooms.go index 458f51de5..36c2c3999 100644 --- a/internal/rooms/rooms.go +++ b/internal/rooms/rooms.go @@ -1207,7 +1207,7 @@ func (r *Room) GetMobs(findTypes ...FindFlag) []int { continue } - if typeFlag&FindHasPet == FindHasPet && mob.Character.Pet.Exists() { + if typeFlag&FindHasPet == FindHasPet && mob.Character.Pet.Exists() && !mob.Character.Pet.IsMissing() { mobMatches = append(mobMatches, mobId) continue } @@ -1297,7 +1297,7 @@ func (r *Room) GetPlayers(findTypes ...FindFlag) []int { continue } - if typeFlag&FindHasPet == FindHasPet && user.Character.Pet.Exists() { + if typeFlag&FindHasPet == FindHasPet && user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { playerMatches = append(playerMatches, userId) continue } diff --git a/internal/scripting/actor_func.go b/internal/scripting/actor_func.go index 056878689..481ba49c2 100644 --- a/internal/scripting/actor_func.go +++ b/internal/scripting/actor_func.go @@ -714,7 +714,7 @@ func (a ScriptActor) GetPet() *ScriptPet { if !a.characterRecord.Pet.Exists() { return nil } - return GetPet(&a.characterRecord.Pet) + return GetPet(&a.characterRecord.Pet, a.userId) } func (a ScriptActor) GrantXP(xpAmt int, reason string) { diff --git a/internal/scripting/pet.go b/internal/scripting/pet.go index 919d3558f..19aaa77d5 100644 --- a/internal/scripting/pet.go +++ b/internal/scripting/pet.go @@ -42,7 +42,7 @@ func TryPetScriptEvent(eventName string, userId int) (bool, error) { return false, errors.New("user has no pet") } - sPet := GetPet(&user.Character.Pet) + sPet := GetPet(&user.Character.Pet, userId) if sPet == nil { return false, errors.New("pet not found") } @@ -112,7 +112,7 @@ func TryPetCommand(cmd string, rest string, userId int) (bool, error) { return false, ErrEventNotFound } - sPet := GetPet(&user.Character.Pet) + sPet := GetPet(&user.Character.Pet, userId) if sPet == nil { return false, ErrEventNotFound } diff --git a/internal/scripting/pet_func.go b/internal/scripting/pet_func.go index ea0816dba..d194610d0 100644 --- a/internal/scripting/pet_func.go +++ b/internal/scripting/pet_func.go @@ -13,6 +13,7 @@ func setPetFunctions(vm *goja.Runtime) { // mutations are reflected immediately without any extra save call. type ScriptPet struct { petRecord *pets.Pet + userId int } // Type returns the pet's type identifier (e.g. "dog", "cat", "owl"). @@ -120,6 +121,41 @@ func (p ScriptPet) ItemCount() int { return 0 } +// IsMissing returns true if the pet is currently absent (MissingCountdown > 0). +func (p ScriptPet) IsMissing() bool { + if p.petRecord != nil { + return p.petRecord.IsMissing() + } + return false +} + +// GoMissing causes the pet to go absent for the given number of rounds. +// Pass 0 to return the pet immediately, firing PetReturn instead of PetLeave. +// Any positive value fires PetLeave and begins the countdown. +func (p ScriptPet) GoMissing(rounds int) { + if p.petRecord == nil { + return + } + if rounds <= 0 { + if !p.petRecord.IsMissing() { + return + } + p.petRecord.GoMissing(0) + if p.userId > 0 { + TryPetScriptEvent(`PetReturn`, p.userId) + } + return + } + if !p.petRecord.IsMissing() { + p.petRecord.GoMissing(rounds) + if p.userId > 0 { + TryPetScriptEvent(`PetLeave`, p.userId) + } + } else { + p.petRecord.GoMissing(rounds) + } +} + // HasScript returns true if this pet type has a script file on disk. func (p ScriptPet) HasScript() bool { if p.petRecord != nil { @@ -143,9 +179,15 @@ func (p ScriptPet) getScript() string { // GetPet returns a ScriptPet wrapping the given pet pointer. // Returns nil if pet is nil or does not exist. -func GetPet(pet *pets.Pet) *ScriptPet { +// Pass the owner's userId as the optional second argument to enable +// script-triggered events such as GoMissing. +func GetPet(pet *pets.Pet, userId ...int) *ScriptPet { if pet == nil || !pet.Exists() { return nil } - return &ScriptPet{petRecord: pet} + sp := &ScriptPet{petRecord: pet} + if len(userId) > 0 { + sp.userId = userId[0] + } + return sp } diff --git a/internal/scripting/schema.go b/internal/scripting/schema.go index 29680873a..b6456dc67 100644 --- a/internal/scripting/schema.go +++ b/internal/scripting/schema.go @@ -405,6 +405,28 @@ func petScriptType() *ScriptTypeDef { ReturnSemantics: "Return value is ignored.", Stub: "function PetAct(pet, actor, room) {\n\n}\n", }, + { + Name: "PetLeave", + Description: "Called when the pet goes missing (GoMissing is invoked with a positive value). Shares the same signature as PetAct.", + Params: []ScriptFuncParam{ + {Name: "pet", Type: "PetObject", Description: "The pet."}, + {Name: "actor", Type: "ActorObject", Description: "The owner of the pet."}, + {Name: "room", Type: "RoomObject", Description: "The room the pet and owner are in."}, + }, + ReturnSemantics: "Return value is ignored.", + Stub: "function PetLeave(pet, actor, room) {\n\n}\n", + }, + { + Name: "PetReturn", + Description: "Called when the pet returns after its MissingCountdown reaches zero. Shares the same signature as PetAct.", + Params: []ScriptFuncParam{ + {Name: "pet", Type: "PetObject", Description: "The pet."}, + {Name: "actor", Type: "ActorObject", Description: "The owner of the pet."}, + {Name: "room", Type: "RoomObject", Description: "The room the pet and owner are in."}, + }, + ReturnSemantics: "Return value is ignored.", + Stub: "function PetReturn(pet, actor, room) {\n\n}\n", + }, { Name: "onCommand", Description: "Called when any command is typed by the pet's owner.", diff --git a/internal/usercommands/feed.go b/internal/usercommands/feed.go index 061e766a5..cf0ac8abd 100644 --- a/internal/usercommands/feed.go +++ b/internal/usercommands/feed.go @@ -12,7 +12,7 @@ import ( func Feed(rest string, user *users.UserRecord, room *rooms.Room, flags events.EventFlag) (bool, error) { - if !user.Character.Pet.Exists() { + if !user.Character.Pet.Exists() || user.Character.Pet.IsMissing() { user.SendText(`You don't have a pet to feed.`) return true, nil } diff --git a/internal/usercommands/get.go b/internal/usercommands/get.go index 6a0958b67..a772e037b 100644 --- a/internal/usercommands/get.go +++ b/internal/usercommands/get.go @@ -75,7 +75,7 @@ func Get(rest string, user *users.UserRecord, room *rooms.Room, flags events.Eve // Look for any pets in the room // petUserId = room.FindByPetName(args[len(args)-1]) - if petUserId == 0 && args[len(args)-1] == `pet` && user.Character.Pet.Exists() { + if petUserId == 0 && args[len(args)-1] == `pet` && user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { petUserId = user.UserId } if petUserId > 0 { diff --git a/internal/usercommands/give.go b/internal/usercommands/give.go index 73799ee50..6f2ba54ff 100644 --- a/internal/usercommands/give.go +++ b/internal/usercommands/give.go @@ -224,7 +224,7 @@ func Give(rest string, user *users.UserRecord, room *rooms.Room, flags events.Ev // Look for any pets in the room // petUserId := room.FindByPetName(giveWho) - if petUserId == 0 && giveWho == `pet` && user.Character.Pet.Exists() { + if petUserId == 0 && giveWho == `pet` && user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { petUserId = user.UserId } if petUserId > 0 { diff --git a/internal/usercommands/go.go b/internal/usercommands/go.go index 5a8c18345..10d7d5bd0 100644 --- a/internal/usercommands/go.go +++ b/internal/usercommands/go.go @@ -199,7 +199,7 @@ func Go(rest string, user *users.UserRecord, room *rooms.Room, flags events.Even )) // Tell the old room they are leaving - if user.Character.Pet.Exists() { + if user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { room.SendText( fmt.Sprintf(string(c.ExitRoomMessageWrapper), @@ -216,7 +216,7 @@ func Go(rest string, user *users.UserRecord, room *rooms.Room, flags events.Even } // Tell everyone if the pet is following - if user.Character.Pet.Exists() { + if user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { user.SendText(fmt.Sprintf(`%s follows you.`, user.Character.Pet.DisplayName())) diff --git a/internal/usercommands/look.go b/internal/usercommands/look.go index 2e7b5a9a3..2c305999b 100644 --- a/internal/usercommands/look.go +++ b/internal/usercommands/look.go @@ -15,6 +15,7 @@ import ( "github.com/GoMudEngine/GoMud/internal/rooms" "github.com/GoMudEngine/GoMud/internal/templates" "github.com/GoMudEngine/GoMud/internal/users" + "github.com/GoMudEngine/GoMud/internal/util" ) func Look(rest string, user *users.UserRecord, room *rooms.Room, flags events.EventFlag) (bool, error) { @@ -347,9 +348,23 @@ func Look(rest string, user *users.UserRecord, room *rooms.Room, flags events.Ev // Look for any pets in the room // petUserId := room.FindByPetName(rest) - if petUserId == 0 && rest == `pet` && user.Character.Pet.Exists() { + if petUserId == 0 && rest == `pet` && user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { petUserId = user.UserId } + // If the user typed "pet" or the pet's name but their own pet is missing, tell them. + if petUserId == 0 && user.Character.Pet.Exists() && user.Character.Pet.IsMissing() { + if rest == `pet` { + user.SendText(fmt.Sprintf(`%s is not here right now.`, user.Character.Pet.DisplayName())) + return true, nil + } + if petName := user.Character.Pet.Name; petName != `` { + if match, _ := util.FindMatchIn(rest, petName); match != `` { + user.SendText(fmt.Sprintf(`%s is not here right now.`, user.Character.Pet.DisplayName())) + return true, nil + } + } + } + if petUserId > 0 { if petUser := users.GetByUserId(petUserId); petUser != nil { diff --git a/internal/usercommands/pet.go b/internal/usercommands/pet.go index 06f6faf5d..afe71d384 100644 --- a/internal/usercommands/pet.go +++ b/internal/usercommands/pet.go @@ -23,7 +23,7 @@ func Pet(rest string, user *users.UserRecord, room *rooms.Room, flags events.Eve if args[0] == `name` { - if !user.Character.Pet.Exists() { + if !user.Character.Pet.Exists() || user.Character.Pet.IsMissing() { user.SendText(`You have no pet to name.`) return true, nil } @@ -69,13 +69,9 @@ func Pet(rest string, user *users.UserRecord, room *rooms.Room, flags events.Eve } petUserId := room.FindByPetName(rest) - if petUserId == 0 && rest == `pet` && user.Character.Pet.Exists() { + if petUserId == 0 && rest == `pet` && user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { petUserId = user.UserId } - if petUserId == 0 { - user.SendText(`Can't find that to pet.`) - return true, nil - } petUser := users.GetByUserId(petUserId) if petUser == nil { diff --git a/internal/usercommands/usercommands.go b/internal/usercommands/usercommands.go index f1973b331..155b7609d 100644 --- a/internal/usercommands/usercommands.go +++ b/internal/usercommands/usercommands.go @@ -337,7 +337,7 @@ func TryCommand(cmd string, rest string, userId int, flags events.EventFlag) (bo } // Check if the pet script intercepts this command - if user.Character.Pet.Exists() { + if user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { if handled, err := scripting.TryPetCommand(cmd, rest, user.UserId); err == nil && handled { return true, nil } diff --git a/modules/gmcp/gmcp.Char.go b/modules/gmcp/gmcp.Char.go index 4778727f3..d1e17a0b7 100644 --- a/modules/gmcp/gmcp.Char.go +++ b/modules/gmcp/gmcp.Char.go @@ -515,7 +515,7 @@ func (g *GMCPCharModule) GetCharNode(user *users.UserRecord, gmcpModule string) payload.Pets = []GMCPCharModule_Payload_Pet{} - if user.Character.Pet.Exists() { + if user.Character.Pet.Exists() && !user.Character.Pet.IsMissing() { p := GMCPCharModule_Payload_Pet{ Name: user.Character.Pet.Name,