Skip to content

Commit

Permalink
First bit of work on adding script support to NPCs
Browse files Browse the repository at this point in the history
  • Loading branch information
mbpolan committed Nov 28, 2023
1 parent c8270d1 commit 433d53e
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 4 deletions.
11 changes: 9 additions & 2 deletions internal/game/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ func NewGame(opts Options) (*Game, error) {
logger.Infof("finished map warm-up in: %s", time.Now().Sub(start))

// add a dummy npc
npc := model.NewNPC()
npc := model.NewNPC(70, "turael")
npc.ID = 4200
npc.DefinitionID = 1
npc.GlobalPos = model.Vector3D{X: 3239, Y: 3429, Z: 0}
ne := newNPCEntity(npc)
g.mapManager.AddNPC(ne, util.GlobalToRegionGlobal(npc.GlobalPos))
Expand Down Expand Up @@ -1795,6 +1794,14 @@ func (g *Game) handleGameUpdate() error {
}
}

// process each npc on this game tick
for _, ne := range g.mapManager.RegionsWithNPCs() {
err := g.scripts.DoNPCOnTick(ne)
if err != nil {
logger.Errorf("failed to execute script for NPC ID %d: %v", ne.npc.ID, err)
}
}

// reconcile the map state now that players have taken their actions
mapUpdates := g.mapManager.Reconcile()

Expand Down
14 changes: 14 additions & 0 deletions internal/game/map_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ func (m *MapManager) RemoveNPC(ne *npcEntity, regionGlobal model.Vector3D) {
}
}

// RegionsWithNPCs returns a map of NPC IDs to their entities that are found on the map.
func (m *MapManager) RegionsWithNPCs() map[int]*npcEntity {
npcs := map[int]*npcEntity{}

// TODO: can npc entities be separated by region?
for _, region := range m.regions {
for id, entity := range region.npcs {
npcs[id] = entity
}
}

return npcs
}

// FindSpectators returns a tuple of players and NPCs that are within visual distance to a player. The keys of the map
// are the player's IDs and NPC IDs, respectively.
func (m *MapManager) FindSpectators(pe *playerEntity) (map[int]*playerEntity, map[int]*npcEntity) {
Expand Down
21 changes: 21 additions & 0 deletions internal/game/script_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
)

const luaTypePlayerEntity = "playerEntity"
const luaTypeNPCEntity = "npcEntity"
const luaTypeInterface = "interface"
const luaTypeItem = "item"

Expand Down Expand Up @@ -117,6 +118,11 @@ func (s *ScriptManager) DoCastSpellOnItem(pe *playerEntity, item *model.Item, sl
s.interfaceType(spell, s.state))
}

// DoNPCOnTick executes an NPC's per-tick logic script.
func (s *ScriptManager) DoNPCOnTick(ne *npcEntity) error {
return s.doFunctionVoid(fmt.Sprintf("npc_%s_on_tick", ne.npc.ScriptSlug), s.npcEntityType(ne, s.state))
}

// playerEntity creates a Lua user-defined data type for a playerEntity.
func (s *ScriptManager) playerEntityType(pe *playerEntity, l *lua.LState) *lua.LUserData {
ud := l.NewUserData()
Expand All @@ -125,6 +131,14 @@ func (s *ScriptManager) playerEntityType(pe *playerEntity, l *lua.LState) *lua.L
return ud
}

// npcEntity creates a Lua user-defined data type for an npcEntity.
func (s *ScriptManager) npcEntityType(ne *npcEntity, l *lua.LState) *lua.LUserData {
ud := l.NewUserData()
ud.Value = ne
ud.Metatable = l.GetTypeMetatable(luaTypeNPCEntity)
return ud
}

// itemType creates a Lua user-defined data type for a model.Item.
func (s *ScriptManager) itemType(item *model.Item, l *lua.LState) *lua.LUserData {
ud := l.NewUserData()
Expand All @@ -147,6 +161,7 @@ func (s *ScriptManager) createState() (*lua.LState, error) {
s.registerInterfaceModel(l)
s.registerItemModel(l)
s.registerPlayerModel(l)
s.registerNPCModel(l)

err := s.registerFunctionProtos(l)
if err != nil {
Expand Down Expand Up @@ -683,6 +698,12 @@ func (s *ScriptManager) registerPlayerModel(l *lua.LState) {
}))
}

// registerNPCModel registers metadata for an npcEntity type.
func (s *ScriptManager) registerNPCModel(l *lua.LState) {
mt := l.NewTypeMetatable(luaTypeNPCEntity)
l.SetGlobal(luaTypeNPCEntity, mt)
}

// registerFunctionProtos executes compiled functions into a Lua state.
func (s *ScriptManager) registerFunctionProtos(l *lua.LState) error {
for _, proto := range s.protos {
Expand Down
8 changes: 6 additions & 2 deletions internal/model/npc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ type NPC struct {
*Entity
// DefinitionID is the identifier for the NPC's appearance.
DefinitionID int
// ScriptSlug is the slug for the game script to execute for this NPC.
ScriptSlug string
}

// NewNPC returns a new NPC model.
func NewNPC() *NPC {
func NewNPC(definitionID int, scriptSlug string) *NPC {
return &NPC{
Entity: &Entity{},
Entity: &Entity{},
DefinitionID: definitionID,
ScriptSlug: scriptSlug,
}
}
9 changes: 9 additions & 0 deletions scripts/npcs/turael.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-------------------------------------
-- Teleport spell library functions
-------------------------------------

--- Handler invoked when the next game tick has started.
-- @param npc The NPC entity to update.
function npc_turael_on_tick(npc)

end

0 comments on commit 433d53e

Please sign in to comment.