diff --git a/_datafiles/world/default/keywords.yaml b/_datafiles/world/default/keywords.yaml
index 3a6340d6..9e0834a9 100644
--- a/_datafiles/world/default/keywords.yaml
+++ b/_datafiles/world/default/keywords.yaml
@@ -87,7 +87,6 @@ help:
- online
- quit
parties:
- - follow
- party
- share
locks:
diff --git a/_datafiles/world/default/mobs/frostfang/scripts/2-guard.js b/_datafiles/world/default/mobs/frostfang/scripts/2-guard.js
index 71d0ce5a..9efd35c2 100644
--- a/_datafiles/world/default/mobs/frostfang/scripts/2-guard.js
+++ b/_datafiles/world/default/mobs/frostfang/scripts/2-guard.js
@@ -9,6 +9,10 @@ var PathTargets = [
function onIdle(mob, room) {
+ if ( mob.PathingAtWaypoint() && mob.IsHome() ) {
+ mob.SetAdjective("patrolling", false);
+ }
+
var random = Math.floor(Math.random() * 10);
switch (random) {
case 0:
@@ -19,8 +23,10 @@ function onIdle(mob, room) {
case 2:
// Start a patrol path
var randomPath = Math.floor(Math.random() * PathTargets.length);
- var selectedPath = PathTargets[randomPath]
+ var selectedPath = PathTargets[randomPath];
+ mob.SetAdjective("patrolling", true);
mob.Command("pathto "+selectedPath.join(' '));
+
return true;
case 3:
// wander randomly.
diff --git a/_datafiles/world/empty/keywords.yaml b/_datafiles/world/empty/keywords.yaml
index 3a6340d6..9e0834a9 100644
--- a/_datafiles/world/empty/keywords.yaml
+++ b/_datafiles/world/empty/keywords.yaml
@@ -87,7 +87,6 @@ help:
- online
- quit
parties:
- - follow
- party
- share
locks:
diff --git a/_datafiles/world/empty/templates/help/follow.template b/_datafiles/world/empty/templates/help/follow.template
deleted file mode 100644
index fa768e34..00000000
--- a/_datafiles/world/empty/templates/help/follow.template
+++ /dev/null
@@ -1,9 +0,0 @@
-.: Help for follow
-
-The follow follows another player so that when they leave a room, you go with them.
-
-Usage:
-
- follow Jim
- This would follow Jim from now on.
-
diff --git a/internal/characters/character.go b/internal/characters/character.go
index f9f559ea..5da6e568 100644
--- a/internal/characters/character.go
+++ b/internal/characters/character.go
@@ -85,7 +85,6 @@ type Character struct {
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
- followers []int // everyone following this user
permaBuffIds []int // Buff Id's that are always present for this character
userId int // User ID of the character if any
}
@@ -532,10 +531,6 @@ func (c *Character) GetRandomItem() (items.Item, bool) {
return c.Items[util.Rand(len(c.Items))], true
}
-func (c *Character) AddFollower(uId int) {
- c.followers = append(c.followers, uId)
-}
-
// USERNAME appears to be
func (c *Character) GetHealthAppearance() string {
@@ -561,10 +556,6 @@ func (c *Character) GetHealthAppearance() string {
return fmt.Sprintf(`%s is in perfect health.`, c.Name, className)
}
-func (c *Character) GetFollowers() []int {
- return append([]int{}, c.followers...)
-}
-
func (c *Character) GetAllSkillRanks() map[string]int {
retMap := make(map[string]int)
for skillName, skillLevel := range c.Skills {
diff --git a/internal/events/eventtypes.go b/internal/events/eventtypes.go
index a5ecee6d..88f36eb7 100644
--- a/internal/events/eventtypes.go
+++ b/internal/events/eventtypes.go
@@ -153,6 +153,13 @@ type NewTurn struct {
func (n NewTurn) Type() string { return `NewTurn` }
+// Anytime a mob is idle
+type MobIdle struct {
+ MobInstanceId int
+}
+
+func (i MobIdle) Type() string { return `MobIdle` }
+
// Gained or lost an item
type EquipmentChange struct {
UserId int
@@ -339,6 +346,8 @@ type Party struct {
Position map[int]string
}
+func (p Party) Type() string { return `Party` }
+
type RedrawPrompt struct {
UserId int
OnlyIfChanged bool
diff --git a/internal/events/listeners.go b/internal/events/listeners.go
index c6a71b55..5b9f79b6 100644
--- a/internal/events/listeners.go
+++ b/internal/events/listeners.go
@@ -91,16 +91,16 @@ func RegisterListener(emptyEvent any, cbFunc Listener, qFlag ...QueueFlag) Liste
} else if listenerDetails.isFinal {
eventListeners[eType] = append(eventListeners[eType], listenerDetails)
- } else {
+ } else { // end of the list, but before any "final" listeners
insertPosition := 0
- for idx := 0; idx < len(eventListeners[eType]); idx++ {
+
+ for idx := len(eventListeners[eType]) - 1; idx >= 0; idx-- {
// If we're looking at a "final" listener, we can't go any farther down the list
if !eventListeners[eType][idx].isFinal {
- insertPosition = idx
- continue
+ insertPosition = idx + 1
+ break
}
- break
}
eventListeners[eType] = append(eventListeners[eType], ListenerWrapper{})
diff --git a/internal/hooks/MobIdle_HandleIdleMobs.go b/internal/hooks/MobIdle_HandleIdleMobs.go
new file mode 100644
index 00000000..5332e0c4
--- /dev/null
+++ b/internal/hooks/MobIdle_HandleIdleMobs.go
@@ -0,0 +1,73 @@
+package hooks
+
+import (
+ "github.com/GoMudEngine/GoMud/internal/configs"
+ "github.com/GoMudEngine/GoMud/internal/events"
+ "github.com/GoMudEngine/GoMud/internal/mobcommands"
+ "github.com/GoMudEngine/GoMud/internal/mobs"
+ "github.com/GoMudEngine/GoMud/internal/rooms"
+ "github.com/GoMudEngine/GoMud/internal/scripting"
+ "github.com/GoMudEngine/GoMud/internal/util"
+)
+
+//
+// Handles default mob idle behavior
+//
+
+func HandleIdleMobs(e events.Event) events.ListenerReturn {
+
+ evt := e.(events.MobIdle)
+
+ mob := mobs.GetInstance(evt.MobInstanceId)
+ if mob == nil {
+ return events.Cancel
+ }
+
+ // if a mob shouldn't be allowed to leave their area (via wandering)
+ // but has somehow been displaced, such as pulling through combat, spells, or otherwise
+ // tell them to path back home
+ if mob.MaxWander == 0 && mob.Character.RoomId != mob.HomeRoomId {
+ mob.Command("pathto home")
+ }
+
+ if mob.CanConverse() && util.Rand(100) < int(configs.GetGamePlayConfig().MobConverseChance) {
+ if mobRoom := rooms.LoadRoom(mob.Character.RoomId); mobRoom != nil {
+ mobcommands.Converse(``, mob, mobRoom) // Execute this directly so that target mob doesn't leave the room before this command executes
+ }
+ }
+
+ // If they have idle commands, maybe do one of them?
+ handled, _ := scripting.TryMobScriptEvent("onIdle", mob.InstanceId, 0, ``, nil)
+ if !handled {
+
+ if !mob.Character.IsCharmed() { // Won't do this stuff if befriended
+
+ if mob.MaxWander > -1 && mob.WanderCount > mob.MaxWander {
+ mob.Command(`pathto home`)
+ }
+
+ }
+
+ //
+ // Look for trouble
+ //
+ if mob.Character.IsCharmed() {
+ // Only some mobs can apply first aid
+ if mob.Character.KnowsFirstAid() {
+ mob.Command(`lookforaid`)
+ }
+ } else {
+
+ idleCmd := `lookfortrouble`
+ if util.Rand(100) < mob.ActivityLevel {
+ idleCmd = mob.GetIdleCommand()
+ if idleCmd == `` {
+ idleCmd = `lookfortrouble`
+ }
+ }
+ mob.Command(idleCmd)
+ }
+ }
+
+ return events.Continue
+}
diff --git a/internal/hooks/NewRound_IdleMobs.go b/internal/hooks/NewRound_IdleMobs.go
index 3df73b40..f89ef515 100644
--- a/internal/hooks/NewRound_IdleMobs.go
+++ b/internal/hooks/NewRound_IdleMobs.go
@@ -8,10 +8,8 @@ import (
"github.com/GoMudEngine/GoMud/internal/configs"
"github.com/GoMudEngine/GoMud/internal/events"
- "github.com/GoMudEngine/GoMud/internal/mobcommands"
"github.com/GoMudEngine/GoMud/internal/mobs"
"github.com/GoMudEngine/GoMud/internal/rooms"
- "github.com/GoMudEngine/GoMud/internal/scripting"
"github.com/GoMudEngine/GoMud/internal/users"
"github.com/GoMudEngine/GoMud/internal/util"
)
@@ -25,10 +23,8 @@ func IdleMobs(e events.Event) events.ListenerReturn {
mobPathAnnounce := false // useful for debugging purposes.
mc := configs.GetMemoryConfig()
- gp := configs.GetGamePlayConfig()
maxBoredom := uint8(mc.MaxMobBoredom)
- globalConverseChance := int(gp.MobConverseChance)
allMobInstances := mobs.GetAllMobInstanceIds()
@@ -143,55 +139,7 @@ func IdleMobs(e events.Event) events.ListenerReturn {
mob.Path.Clear()
}
- // if a mob shouldn't be allowed to leave their area (via wandering)
- // but has somehow been displaced, such as pulling through combat, spells, or otherwise
- // tell them to path back home
- if mob.MaxWander == 0 && mob.Character.RoomId != mob.HomeRoomId {
- mob.Command("pathto home")
- continue
- }
-
- if mob.CanConverse() && util.Rand(100) < globalConverseChance {
- if mobRoom := rooms.LoadRoom(mob.Character.RoomId); mobRoom != nil {
- mobcommands.Converse(``, mob, mobRoom) // Execute this directly so that target mob doesn't leave the room before this command executes
- //mob.Command(`converse`)
- }
- continue
- }
-
- // If they have idle commands, maybe do one of them?
- handled, _ := scripting.TryMobScriptEvent("onIdle", mob.InstanceId, 0, ``, nil)
- if !handled {
-
- if !mob.Character.IsCharmed() { // Won't do this stuff if befriended
-
- if mob.MaxWander > -1 && mob.WanderCount > mob.MaxWander {
- mob.Command(`pathto home`)
- continue
- }
-
- }
-
- //
- // Look for trouble
- //
- if mob.Character.IsCharmed() {
- // Only some mobs can apply first aid
- if mob.Character.KnowsFirstAid() {
- mob.Command(`lookforaid`)
- }
- } else {
-
- idleCmd := `lookfortrouble`
- if util.Rand(100) < mob.ActivityLevel {
- idleCmd = mob.GetIdleCommand()
- if idleCmd == `` {
- idleCmd = `lookfortrouble`
- }
- }
- mob.Command(idleCmd)
- }
- }
+ events.AddToQueue(events.MobIdle{MobInstanceId: mobId})
}
diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go
index 717614d3..b2874a95 100644
--- a/internal/hooks/hooks.go
+++ b/internal/hooks/hooks.go
@@ -31,6 +31,7 @@ func RegisterListeners() {
//
events.RegisterListener(events.NewRound{}, AutoHeal)
events.RegisterListener(events.NewRound{}, IdleMobs)
+ events.RegisterListener(events.MobIdle{}, HandleIdleMobs)
// Turn Hooks
events.RegisterListener(events.NewTurn{}, CleanupZombies)
diff --git a/internal/mobcommands/mobcommands.go b/internal/mobcommands/mobcommands.go
index 1845d613..2b986e9b 100644
--- a/internal/mobcommands/mobcommands.go
+++ b/internal/mobcommands/mobcommands.go
@@ -110,6 +110,24 @@ func TryCommand(cmd string, rest string, mobId int) (bool, error) {
}
*/
+ if alias := keywords.TryCommandAlias(cmd); alias != cmd {
+ // If it's a multi-word aliase, we need to extract the first word to replace the command
+ // The rest will be combined with any "rest" the mob provided.
+ if strings.Contains(alias, ` `) {
+ parts := strings.Split(alias, ` `)
+ // grab the first word as the new cmd
+ cmd = parts[0]
+ // Add the "rest" to the end if any
+ if len(rest) > 0 {
+ rest = strings.TrimPrefix(alias, cmd+` `) + ` ` + rest
+ } else {
+ rest = strings.TrimPrefix(alias, cmd+` `)
+ }
+ } else {
+ cmd = alias
+ }
+ }
+
if cmdInfo, ok := mobCommands[cmd]; ok {
if mobDisabled && !cmdInfo.AllowedWhenDowned {
diff --git a/internal/scripting/actor_func.go b/internal/scripting/actor_func.go
index 144442ad..686692f0 100644
--- a/internal/scripting/actor_func.go
+++ b/internal/scripting/actor_func.go
@@ -597,6 +597,13 @@ func (a ScriptActor) SetAdjective(adj string, addIt bool) {
a.characterRecord.SetAdjective(adj, addIt)
}
+func (a ScriptActor) IsHome() bool {
+ if a.mobRecord != nil {
+ return a.mobRecord.HomeRoomId == a.characterRecord.RoomId
+ }
+ return false
+}
+
func (a ScriptActor) GetCharmCount() int {
return len(a.characterRecord.GetCharmIds())
}
diff --git a/internal/usercommands/follow.go b/internal/usercommands/follow.go
deleted file mode 100644
index d4a1692e..00000000
--- a/internal/usercommands/follow.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package usercommands
-
-import (
- "fmt"
-
- "github.com/GoMudEngine/GoMud/internal/events"
- "github.com/GoMudEngine/GoMud/internal/rooms"
- "github.com/GoMudEngine/GoMud/internal/users"
-)
-
-func Follow(rest string, user *users.UserRecord, room *rooms.Room, flags events.EventFlag) (bool, error) {
-
- if rest == "" {
- user.SendText("Follow whom?")
- return true, nil
- }
-
- playerId, _ := room.FindByName(rest)
-
- if playerId == 0 {
- user.SendText(fmt.Sprintf(`%s - not found`, rest))
- return true, nil
- }
-
- if playerId == user.UserId {
- user.SendText(`You can't follow yourself`)
- return true, nil
- }
-
- if playerId > 0 {
-
- followUser := users.GetByUserId(playerId)
-
- user.SendText(
- fmt.Sprintf(`You follow %s.`, followUser.Character.Name),
- )
-
- followUser.SendText(
- fmt.Sprintf(`%s is following you.`, user.Character.Name),
- )
-
- followUser.Character.AddFollower(user.UserId)
- }
-
- return true, nil
-}
diff --git a/internal/usercommands/usercommands.go b/internal/usercommands/usercommands.go
index f0b54303..0b89648e 100644
--- a/internal/usercommands/usercommands.go
+++ b/internal/usercommands/usercommands.go
@@ -77,7 +77,6 @@ var (
`experience`: {Experience, true, false},
`equip`: {Equip, false, false},
`flee`: {Flee, false, false},
- `follow`: {Follow, false, false},
`gearup`: {Gearup, false, false},
`get`: {Get, false, false},
`give`: {Give, false, false},
@@ -283,20 +282,36 @@ func TryCommand(cmd string, rest string, userId int, flags events.EventFlag) (bo
} else {
if alias := user.TryCommandAlias(cmd); alias != cmd {
+ // If it's a multi-word aliase, we need to extract the first word to replace the command
+ // The rest will be combined with any "rest" the player provided.
if strings.Contains(alias, ` `) {
parts := strings.Split(alias, ` `)
- cmd = parts[0] // grab the first word as the new cmd
- rest = strings.TrimPrefix(alias, cmd+` `) + ` ` + rest // add the remaining alias to the rest
+ // grab the first word as the new cmd
+ cmd = parts[0]
+ // Add the "rest" to the end if any
+ if len(rest) > 0 {
+ rest = strings.TrimPrefix(alias, cmd+` `) + ` ` + rest
+ } else {
+ rest = strings.TrimPrefix(alias, cmd+` `)
+ }
} else {
cmd = alias
}
}
if alias := keywords.TryCommandAlias(cmd); alias != cmd {
+ // If it's a multi-word aliase, we need to extract the first word to replace the command
+ // The rest will be combined with any "rest" the player provided.
if strings.Contains(alias, ` `) {
parts := strings.Split(alias, ` `)
- cmd = parts[0] // grab the first word as the new cmd
- rest = strings.TrimPrefix(alias, cmd+` `) + ` ` + rest // add the remaining alias to the rest
+ // grab the first word as the new cmd
+ cmd = parts[0]
+ // Add the "rest" to the end if any
+ if len(rest) > 0 {
+ rest = strings.TrimPrefix(alias, cmd+` `) + ` ` + rest
+ } else {
+ rest = strings.TrimPrefix(alias, cmd+` `)
+ }
} else {
cmd = alias
}
diff --git a/modules/follow/files/data-overlays/keywords.yaml b/modules/follow/files/data-overlays/keywords.yaml
new file mode 100644
index 00000000..84b945a9
--- /dev/null
+++ b/modules/follow/files/data-overlays/keywords.yaml
@@ -0,0 +1,7 @@
+help:
+ command:
+ parties:
+ - follow
+command-aliases:
+ 'follow stop': ['unfollow']
+ 'follow lose': ['lose']
diff --git a/_datafiles/world/default/templates/help/follow.template b/modules/follow/files/datafiles/templates/help/follow.template
similarity index 67%
rename from _datafiles/world/default/templates/help/follow.template
rename to modules/follow/files/datafiles/templates/help/follow.template
index fa768e34..642f410d 100644
--- a/_datafiles/world/default/templates/help/follow.template
+++ b/modules/follow/files/datafiles/templates/help/follow.template
@@ -7,3 +7,9 @@ The follow follows another player so that when they le
follow Jim
This would follow Jim from now on.
+ follow stop
+ Stop following anyone
+
+ follow lose
+ Shake off anyone following you, so that they aren't anymore.
+
diff --git a/modules/follow/follow.go b/modules/follow/follow.go
new file mode 100644
index 00000000..7043d181
--- /dev/null
+++ b/modules/follow/follow.go
@@ -0,0 +1,484 @@
+package follow
+
+import (
+ "embed"
+ "fmt"
+ "strings"
+
+ "github.com/GoMudEngine/GoMud/internal/events"
+ "github.com/GoMudEngine/GoMud/internal/mobs"
+ "github.com/GoMudEngine/GoMud/internal/parties"
+ "github.com/GoMudEngine/GoMud/internal/plugins"
+ "github.com/GoMudEngine/GoMud/internal/rooms"
+ "github.com/GoMudEngine/GoMud/internal/users"
+ "github.com/GoMudEngine/GoMud/internal/util"
+)
+
+var (
+
+ //////////////////////////////////////////////////////////////////////
+ // NOTE: The below //go:embed directive is important!
+ // It embeds the relative path into the var below it.
+ //////////////////////////////////////////////////////////////////////
+
+ //go:embed files/*
+ files embed.FS
+)
+
+// ////////////////////////////////////////////////////////////////////
+// NOTE: The init function in Go is a special function that is
+// automatically executed before the main function within a package.
+// It is used to initialize variables, set up configurations, or
+// perform any other setup tasks that need to be done before the
+// program starts running.
+// ////////////////////////////////////////////////////////////////////
+func init() {
+ //
+ // We can use all functions only, but this demonstrates
+ // how to use a struct
+ //
+ f := FollowModule{
+ plug: plugins.New(`follow`, `1.0`),
+ followed: make(map[followId][]followId),
+ followers: make(map[followId]followId),
+ }
+
+ //
+ // Add the embedded filesystem
+ //
+ if err := f.plug.AttachFileSystem(files); err != nil {
+ panic(err)
+ }
+ //
+ // Register any user/mob commands
+ //
+ f.plug.AddUserCommand(`follow`, f.followUserCommand, true, false)
+ f.plug.AddMobCommand(`follow`, f.followMobCommand, true)
+
+ events.RegisterListener(events.RoomChange{}, f.roomChangeHandler)
+ events.RegisterListener(events.PlayerDespawn{}, f.playerDespawnHandler)
+ events.RegisterListener(events.MobIdle{}, f.idleMobHandler, events.First)
+ events.RegisterListener(events.PartyUpdated{}, f.onPartyChange)
+}
+
+//////////////////////////////////////////////////////////////////////
+// NOTE: What follows is all custom code. For this module.
+//////////////////////////////////////////////////////////////////////
+
+type followId struct {
+ userId int
+ mobInstanceId int
+}
+
+// Using a struct gives a way to store longer term data.
+type FollowModule struct {
+ // Keep a reference to the plugin when we create it so that we can call ReadBytes() and WriteBytes() on it.
+ plug *plugins.Plugin
+
+ followed map[followId][]followId // key => who's followed. value ([]followId{}) => who's following them
+ followers map[followId]followId // key => who's following someone. value => who's being followed
+}
+
+// Get all followeres attached to a target
+func (f *FollowModule) isFollowing(followCheck followId) bool {
+ _, ok := f.followers[followCheck]
+ return ok
+}
+
+// Get all followeres attached to a target
+func (f *FollowModule) getFollowers(followTarget followId) []followId {
+
+ if _, ok := f.followed[followTarget]; !ok {
+ return []followId{}
+ }
+
+ followerResults := make([]followId, len(f.followed[followTarget]))
+ copy(followerResults, f.followed[followTarget])
+
+ return followerResults
+}
+
+// Add a single follower to a target
+func (f *FollowModule) startFollow(followTarget followId, followSource followId) {
+
+ // Make sure they no longer follow whoever they were before.
+ f.stopFollowing(followSource)
+
+ f.followers[followSource] = followTarget
+ if _, ok := f.followed[followTarget]; !ok {
+ f.followed[followTarget] = []followId{}
+ }
+
+ f.followed[followTarget] = append(f.followed[followTarget], followSource)
+}
+
+// Remove a single follower from whoever they are following (if any)
+func (f *FollowModule) stopFollowing(followSource followId) followId {
+
+ wasFollowing := followId{}
+
+ if followTarget, ok := f.followers[followSource]; ok {
+ delete(f.followers, followSource)
+
+ wasFollowing = followTarget
+
+ for idx, fId := range f.followed[followTarget] {
+ if fId == followSource {
+ f.followed[followTarget] = append(f.followed[followTarget][0:idx], f.followed[followTarget][idx+1:]...)
+
+ if len(f.followed[followTarget]) == 0 {
+ delete(f.followed, followTarget)
+ }
+
+ break
+ }
+ }
+ }
+
+ return wasFollowing
+}
+
+// Remove all followers from a target
+func (f *FollowModule) loseFollowers(followTarget followId) []followId {
+ allFollowers := f.getFollowers(followTarget)
+ for _, followSource := range allFollowers {
+ f.stopFollowing(followSource)
+ }
+ return allFollowers
+}
+
+//
+// Event Handlers
+//
+
+// If players make changes (into/out of party)
+// Just make sure they aren't following anyone.
+// This is just basic cleanup/precaution
+func (f *FollowModule) onPartyChange(e events.Event) events.ListenerReturn {
+
+ evt := e.(events.PartyUpdated)
+
+ for _, uId := range evt.UserIds {
+ f.stopFollowing(followId{userId: uId})
+ }
+
+ return events.Continue
+}
+
+// Interrupt the idle action of mobs if they are currently following someone.
+func (f *FollowModule) idleMobHandler(e events.Event) events.ListenerReturn {
+ evt := e.(events.MobIdle)
+
+ if f.isFollowing(followId{mobInstanceId: evt.MobInstanceId}) {
+ return events.Cancel
+ }
+
+ return events.Continue
+}
+
+func (f *FollowModule) roomChangeHandler(e events.Event) events.ListenerReturn {
+ evt := e.(events.RoomChange)
+
+ moverId := followId{userId: evt.UserId, mobInstanceId: evt.MobInstanceId}
+
+ allFollowers := f.getFollowers(moverId)
+ if len(allFollowers) == 0 {
+ return events.Continue
+ }
+
+ fromRoom := rooms.LoadRoom(evt.FromRoomId)
+ if fromRoom == nil {
+ return events.Continue
+ }
+
+ followExitName := ``
+ for exitName, exitInfo := range fromRoom.Exits {
+ if exitInfo.RoomId == evt.ToRoomId {
+ followExitName = exitName
+ break
+ }
+ }
+
+ if followExitName == `` {
+ for exitName, exitInfo := range fromRoom.ExitsTemp {
+ if exitInfo.RoomId == evt.ToRoomId {
+ followExitName = exitName
+ break
+ }
+ }
+ }
+
+ // The exit they went through is gone/missing? (Teleported?)
+ // End the follow
+ if followExitName == `` {
+ if evt.UserId > 0 {
+ if user := users.GetByUserId(evt.UserId); user != nil {
+ user.Command(`follow lose`)
+ }
+ }
+ } else {
+
+ for _, fId := range allFollowers {
+
+ if fId.mobInstanceId > 0 {
+
+ if mob := mobs.GetInstance(fId.mobInstanceId); mob != nil {
+ if fromRoom.RoomId == mob.Character.RoomId {
+ mob.Command(followExitName, .25)
+ continue
+ }
+
+ mob.Command(`follow stop`)
+ }
+ f.stopFollowing(fId)
+
+ } else if fId.userId > 0 {
+
+ if user := users.GetByUserId(fId.userId); user != nil {
+ if fromRoom.RoomId == user.Character.RoomId {
+ user.Command(followExitName, .25)
+ continue
+ }
+
+ user.Command(`follow stop`)
+ }
+ f.stopFollowing(fId)
+
+ }
+
+ }
+
+ }
+
+ return events.Continue
+}
+
+func (f *FollowModule) playerDespawnHandler(e events.Event) events.ListenerReturn {
+ // Don't really care about the event data for this
+ evt, typeOk := e.(events.PlayerDespawn)
+ if !typeOk {
+ return events.Cancel
+ }
+
+ f.loseFollowers(followId{userId: evt.UserId})
+
+ return events.Continue
+}
+
+//
+// Commands
+//
+
+func (f *FollowModule) followUserCommand(rest string, user *users.UserRecord, room *rooms.Room, flags events.EventFlag) (bool, error) {
+
+ if rest == "" {
+ user.SendText(`Follow whom? Try help command`)
+ return true, nil
+ }
+
+ if parties.Get(user.UserId) != nil {
+ user.SendText(`You can't use this command while in a party.`)
+ return true, nil
+ }
+
+ args := util.SplitButRespectQuotes(strings.ToLower(rest))
+
+ followTargetName := args[0]
+ followAction := `follow`
+
+ if rest == `stop` || rest == `lose` {
+ followAction = rest
+ followTargetName = ``
+ }
+
+ userId, mobInstId := 0, 0
+ if len(followTargetName) > 0 {
+ userId, mobInstId = room.FindByName(followTargetName)
+ }
+
+ followCommandTarget := followId{userId: userId, mobInstanceId: mobInstId}
+ followCommandSource := followId{userId: user.UserId}
+
+ if followCommandTarget.userId == followCommandSource.userId {
+ user.SendText(`You can't target yourself.`)
+ return true, nil
+ }
+
+ // Lose any followers
+ if followAction == `lose` {
+
+ if lostFollowers := f.loseFollowers(followCommandSource); len(lostFollowers) > 0 {
+
+ // Tell all the followers they
+ for _, fId := range lostFollowers {
+ if fId.userId == 0 {
+ continue
+ }
+
+ if followerUser := users.GetByUserId(fId.userId); followerUser != nil {
+ followerUser.SendText(fmt.Sprintf(`You are no longer following %s.`, user.Character.Name))
+ }
+ }
+
+ }
+
+ user.SendText(fmt.Sprintf(`Nobody is following you.`))
+
+ return true, nil
+ }
+
+ // Stop following someone?
+ if followAction == `stop` {
+
+ wasFollowing := f.stopFollowing(followCommandSource)
+
+ if wasFollowing.userId > 0 {
+
+ if followUser := users.GetByUserId(wasFollowing.userId); followUser != nil {
+ followUser.SendText(fmt.Sprintf(`%s stopped following you.`, followUser.Character.Name))
+ user.SendText(fmt.Sprintf(`You are no longer following %s.`, followUser.Character.Name))
+ return true, nil
+ }
+
+ }
+
+ if wasFollowing.mobInstanceId > 0 {
+
+ if followMob := mobs.GetInstance(wasFollowing.mobInstanceId); followMob != nil {
+ user.SendText(fmt.Sprintf(`You are no longer following %s.`, followMob.Character.Name))
+ return true, nil
+ }
+
+ }
+
+ user.SendText(`You aren't following anyone.`)
+
+ return true, nil
+ }
+
+ // Default behavior is follow
+ if followCommandTarget.userId > 0 {
+
+ f.startFollow(followCommandTarget, followCommandSource)
+
+ targetUser := users.GetByUserId(followCommandTarget.userId)
+
+ user.SendText(fmt.Sprintf(`You start following %s.`, targetUser.Character.Name))
+
+ targetUser.SendText(fmt.Sprintf(`%s is following you.`, user.Character.Name))
+
+ return true, nil
+ }
+
+ if followCommandTarget.mobInstanceId > 0 {
+
+ targetMob := mobs.GetInstance(followCommandTarget.mobInstanceId)
+
+ if targetMob.HatesAlignment(user.Character.Alignment) {
+ user.SendText(fmt.Sprintf(`%s won't let you follow them.`, targetMob.Character.Name))
+ } else {
+ f.startFollow(followCommandTarget, followCommandSource)
+
+ user.SendText(fmt.Sprintf(`You start following %s.`, targetMob.Character.Name))
+ }
+
+ return true, nil
+ }
+
+ user.SendText(`Follow whom?`)
+
+ return true, nil
+}
+
+func (f *FollowModule) followMobCommand(rest string, mob *mobs.Mob, room *rooms.Room) (bool, error) {
+
+ if rest == "" {
+ return true, nil
+ }
+
+ args := util.SplitButRespectQuotes(strings.ToLower(rest))
+
+ followTargetName := args[0]
+ followAction := `follow`
+
+ if rest == `stop` || rest == `lose` {
+ followAction = rest
+ followTargetName = ``
+ }
+
+ userId, mobInstId := 0, 0
+ if len(followTargetName) > 0 {
+ userId, mobInstId = room.FindByName(followTargetName)
+ }
+
+ followCommandTarget := followId{userId: userId, mobInstanceId: mobInstId}
+ followCommandSource := followId{mobInstanceId: mob.InstanceId}
+
+ if followCommandTarget.mobInstanceId == followCommandSource.mobInstanceId {
+ return true, nil
+ }
+
+ // Lose any followers
+ if followAction == `lose` {
+
+ if lostFollowers := f.loseFollowers(followCommandSource); len(lostFollowers) > 0 {
+
+ // Tell all the followers they
+ for _, fId := range lostFollowers {
+ if fId.userId == 0 {
+ continue
+ }
+
+ if followerUser := users.GetByUserId(fId.userId); followerUser != nil {
+ followerUser.SendText(fmt.Sprintf(`You are no longer following %s.`, mob.Character.Name))
+ }
+ }
+
+ return true, nil
+ }
+
+ return true, nil
+ }
+
+ // Stop following someone?
+ if followAction == `stop` {
+
+ wasFollowing := f.stopFollowing(followCommandSource)
+
+ if wasFollowing.userId > 0 {
+
+ if followUser := users.GetByUserId(wasFollowing.userId); followUser != nil {
+ followUser.SendText(fmt.Sprintf(`%s stopped following you.`, followUser.Character.Name))
+ return true, nil
+ }
+
+ }
+
+ return true, nil
+ }
+
+ // Default behavior is follow
+
+ // If they are on a path, clear it. The follow takes priority.
+ mob.Path.Clear()
+
+ if followCommandTarget.userId > 0 {
+
+ f.startFollow(followCommandTarget, followCommandSource)
+
+ targetUser := users.GetByUserId(followCommandTarget.userId)
+
+ targetUser.SendText(fmt.Sprintf(`%s is following you.`, mob.Character.Name))
+
+ return true, nil
+ }
+
+ if followCommandTarget.mobInstanceId > 0 {
+
+ f.startFollow(followCommandTarget, followCommandSource)
+
+ return true, nil
+ }
+
+ return false, nil
+}