aboutsummaryrefslogtreecommitdiff
path: root/internal/game/sys_safespot.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/sys_safespot.go')
-rw-r--r--internal/game/sys_safespot.go330
1 files changed, 330 insertions, 0 deletions
diff --git a/internal/game/sys_safespot.go b/internal/game/sys_safespot.go
new file mode 100644
index 0000000..ffb7948
--- /dev/null
+++ b/internal/game/sys_safespot.go
@@ -0,0 +1,330 @@
+package game
+
+import (
+ "fmt"
+ "math/rand"
+
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+var sizeRank = map[string]int{
+ "small": 1,
+ "medium": 2,
+ "large": 3,
+ "massive": 4,
+}
+
+func (g *Game) isSafespotted(playerName string) bool {
+ g.safespotMu.Lock()
+ defer g.safespotMu.Unlock()
+ ss, ok := g.safespotStates[playerName]
+ return ok && ss.Active && ss.HideCountdown <= 0
+}
+
+func (g *Game) safespotTier(p *player.Player) int {
+ tier := 1
+ if getPlayerFlagInt(p, "unlocked_rock_cover") > 0 {
+ tier++
+ }
+ return tier
+}
+
+func blocksMob(maxBlockSize, mobSize string) bool {
+ if maxBlockSize == "" {
+ return true
+ }
+ max := sizeRank[maxBlockSize]
+ mob := sizeRank[mobSize]
+ if mob == 0 {
+ mob = sizeRank["medium"]
+ }
+ return mob <= max
+}
+
+func (g *Game) safespotBlocksMob(p *player.Player, mob *world.MobInstance) bool {
+ g.safespotMu.Lock()
+ ss, ok := g.safespotStates[p.Name]
+ g.safespotMu.Unlock()
+ if !ok || !ss.Active || ss.HideCountdown > 0 {
+ return false
+ }
+
+ objDef, err := g.ObjectStore.Load(ss.ObjectDefID)
+ if err != nil || objDef.Safespot == nil {
+ return false
+ }
+
+ if !blocksMob(objDef.Safespot.MaxBlockSize, mob.Size) {
+ return false
+ }
+
+ if mob.AttackType == "ranged" || mob.AttackType == "science" {
+ return false
+ }
+
+ return true
+}
+
+func (g *Game) sessionInRoom(name string, roomSessions []*net.Session) *net.Session {
+ for _, sess := range roomSessions {
+ if sess.Player != nil && sess.Player.Name == name {
+ return sess
+ }
+ }
+ return nil
+}
+
+func (g *Game) activateSafespot(sess *net.Session, p *player.Player, ss *SafespotState) {
+ objDef, err := g.ObjectStore.Load(ss.ObjectDefID)
+ if err != nil || objDef.Safespot == nil {
+ g.safespotMu.Lock()
+ delete(g.safespotStates, p.Name)
+ g.safespotMu.Unlock()
+ sess.WriteLine("The safespot no longer exists.")
+ return
+ }
+
+ cfg := objDef.Safespot
+ st := g.World.GetObjState(ss.RoomID, ss.ObjectDefID, ss.ObjectIndex)
+ if st == nil {
+ g.safespotMu.Lock()
+ delete(g.safespotStates, p.Name)
+ g.safespotMu.Unlock()
+ sess.WriteLine("The safespot no longer exists.")
+ return
+ }
+
+ if cfg.RespawnOnHide {
+ st.SafespotLevel = len(cfg.Levels)
+ st.SafespotTicksAtLevel = 0
+ } else if st.SafespotLevel <= 0 {
+ g.safespotMu.Lock()
+ delete(g.safespotStates, p.Name)
+ g.safespotMu.Unlock()
+ sess.WriteLine(fmt.Sprintf("The %s has been completely destroyed.", objDef.Name))
+ return
+ }
+
+ st.SafespotOccupants = append(st.SafespotOccupants, p.Name)
+ p.ActionState = nil
+
+ var msg string
+ levelIdx := st.SafespotLevel - 1
+ if levelIdx >= 0 && levelIdx < len(cfg.Levels) && cfg.Levels[levelIdx].Message != "" {
+ msg = cfg.Levels[levelIdx].Message
+ } else {
+ msg = fmt.Sprintf("You crouch behind the %s, using it as cover.", objDef.Name)
+ }
+ sess.WriteLine(g.colorize(sess, "combat_info", msg))
+}
+
+func (g *Game) safespotMaintenance(sess *net.Session, p *player.Player, ss *SafespotState) {
+ objDef, err := g.ObjectStore.Load(ss.ObjectDefID)
+ if err != nil || objDef.Safespot == nil {
+ g.forceLeaveSafespot(sess, p, ss, "The safespot no longer exists.")
+ return
+ }
+
+ cfg := objDef.Safespot
+ st := g.World.GetObjState(ss.RoomID, ss.ObjectDefID, ss.ObjectIndex)
+ if st == nil {
+ g.forceLeaveSafespot(sess, p, ss, "The safespot no longer exists.")
+ return
+ }
+
+ if st.SafespotLevel <= 0 {
+ if cfg.RespawnTicks > 0 {
+ return
+ }
+ g.forceLeaveSafespot(sess, p, ss, fmt.Sprintf("The %s has crumbled away!", objDef.Name))
+ return
+ }
+
+ if len(st.SafespotOccupants) == 0 {
+ return
+ }
+
+ if cfg.UnsafeChance > 0 && rand.Float64() < cfg.UnsafeChance {
+ alert := p.OptionString("safespot_alert")
+ g.forceLeaveSafespot(sess, p, ss, "You slip and are no longer covered!")
+ if alert != "" {
+ sess.WriteLine(color.ExpandTags(g.colorMode(sess), alert))
+ }
+ return
+ }
+
+ levelIdx := st.SafespotLevel - 1
+ degraded := false
+
+ if cfg.DecayChance > 0 && rand.Float64() < cfg.DecayChance {
+ degraded = true
+ }
+
+ if !degraded && cfg.DecayTicks > 0 {
+ st.SafespotTicksAtLevel++
+ if st.SafespotTicksAtLevel >= engine.ToTicks(cfg.DecayTicks) {
+ degraded = true
+ }
+ }
+
+ if degraded {
+ g.degradeSafespot(st, objDef, cfg, levelIdx)
+ }
+}
+
+func (g *Game) degradeSafespot(st *world.ObjState, objDef *object.ObjectDef, cfg *object.SafespotConfig, currentLevelIdx int) {
+ roomSessions := g.Hub.PlayersInRoom(st.RoomID)
+
+ if currentLevelIdx >= 0 && currentLevelIdx < len(cfg.Levels) {
+ msg := cfg.Levels[currentLevelIdx].DegradeMessage
+ if msg != "" {
+ for _, rs := range roomSessions {
+ rs.WriteLine("")
+ rs.WriteLine(color.ExpandTags(g.colorMode(rs), msg))
+ }
+ }
+ }
+
+ st.SafespotLevel--
+ st.SafespotTicksAtLevel = 0
+
+ if st.SafespotLevel <= 0 {
+ for _, name := range st.SafespotOccupants {
+ g.safespotMu.Lock()
+ delete(g.safespotStates, name)
+ g.safespotMu.Unlock()
+ if occSess := g.sessionInRoom(name, roomSessions); occSess != nil {
+ occSess.WriteLine(color.ExpandTags(g.colorMode(occSess),
+ fmt.Sprintf("The %s crumbles away!", objDef.Name)))
+ if occSess.Player != nil {
+ alert := occSess.Player.OptionString("safespot_alert")
+ if alert != "" {
+ occSess.WriteLine(color.ExpandTags(g.colorMode(occSess), alert))
+ }
+ }
+ g.writePrompt(occSess)
+ }
+ }
+ st.SafespotOccupants = nil
+ g.scheduleSafespotRespawn(st, objDef, cfg)
+ return
+ }
+
+ newLevelIdx := st.SafespotLevel - 1
+ if newLevelIdx >= 0 && newLevelIdx < len(cfg.Levels) && cfg.Levels[newLevelIdx].Message != "" {
+ msg := cfg.Levels[newLevelIdx].Message
+ for _, name := range st.SafespotOccupants {
+ if occSess := g.sessionInRoom(name, roomSessions); occSess != nil {
+ occSess.WriteLine("")
+ occSess.WriteLine(color.ExpandTags(g.colorMode(occSess), msg))
+ }
+ }
+ }
+}
+
+func (g *Game) scheduleSafespotRespawn(st *world.ObjState, objDef *object.ObjectDef, cfg *object.SafespotConfig) {
+ if cfg.RespawnTicks <= 0 {
+ return
+ }
+ roomID := st.RoomID
+ defID := objDef.ID
+ g.Ticks.Subscribe(engine.ToTicks(cfg.RespawnTicks), func() bool {
+ respawnSt := g.World.GetObjState(roomID, defID, 0)
+ if respawnSt == nil {
+ return false
+ }
+ respawnSt.SafespotLevel = len(cfg.Levels)
+ respawnSt.SafespotTicksAtLevel = 0
+ for _, sess := range g.Hub.PlayersInRoom(roomID) {
+ sess.WriteLine("")
+ sess.WriteLine(color.ExpandTags(g.colorMode(sess),
+ fmt.Sprintf("The %s reforms and looks stable again.", objDef.Name)))
+ }
+ return false
+ })
+}
+
+func (g *Game) leaveSafespot(sess *net.Session, p *player.Player) {
+ g.safespotMu.Lock()
+ ss, ok := g.safespotStates[p.Name]
+ if !ok {
+ g.safespotMu.Unlock()
+ return
+ }
+ delete(g.safespotStates, p.Name)
+ g.safespotMu.Unlock()
+
+ g.removeSafespotOccupant(p.Name, ss)
+
+ objDef, _ := g.ObjectStore.Load(ss.ObjectDefID)
+ objName := "cover"
+ if objDef != nil {
+ objName = objDef.Name
+ }
+ sess.WriteLine(fmt.Sprintf("You step out from behind the %s.", objName))
+ p.ActionState = nil
+}
+
+func (g *Game) forceLeaveSafespot(sess *net.Session, p *player.Player, ss *SafespotState, reason string) {
+ g.safespotMu.Lock()
+ delete(g.safespotStates, p.Name)
+ g.safespotMu.Unlock()
+
+ g.removeSafespotOccupant(p.Name, ss)
+
+ if reason != "" {
+ sess.WriteLine(reason)
+ }
+ p.ActionState = nil
+}
+
+func (g *Game) removeSafespotOccupant(playerName string, ss *SafespotState) {
+ st := g.World.GetObjState(ss.RoomID, ss.ObjectDefID, ss.ObjectIndex)
+ if st == nil {
+ return
+ }
+ for i, name := range st.SafespotOccupants {
+ if name == playerName {
+ st.SafespotOccupants = append(st.SafespotOccupants[:i], st.SafespotOccupants[i+1:]...)
+ return
+ }
+ }
+}
+
+func (g *Game) SafespotTick() {
+ if g.Hub == nil {
+ return
+ }
+ for _, sess := range g.Hub.AllSessions() {
+ p := sess.Player
+ if p == nil {
+ continue
+ }
+
+ g.safespotMu.Lock()
+ ss, ok := g.safespotStates[p.Name]
+ if !ok || !ss.Active {
+ g.safespotMu.Unlock()
+ continue
+ }
+
+ if ss.HideCountdown > 0 {
+ ss.HideCountdown--
+ if ss.HideCountdown > 0 {
+ g.safespotMu.Unlock()
+ continue
+ }
+ g.safespotMu.Unlock()
+ g.activateSafespot(sess, p, ss)
+ continue
+ }
+ g.safespotMu.Unlock()
+
+ g.safespotMaintenance(sess, p, ss)
+ }
+}