made RoomData for data between entities. made Alignment separate class, holding a Faction. used this for more efficient enemy detection in AI
This commit is contained in:
parent
fc0f9bde9d
commit
7f398ebe12
|
@ -37,7 +37,6 @@ The idea is to make 3 different kind of areas:
|
|||
|
||||
- better code documentation
|
||||
- test server robustness
|
||||
- more efficient target detection for monsters
|
||||
- loot
|
||||
- make items usable
|
||||
- more content (rooms, objects etc)
|
||||
|
@ -52,6 +51,7 @@ The idea is to make 3 different kind of areas:
|
|||
|
||||
## DONE
|
||||
|
||||
- more efficient target detection for monsters
|
||||
- more efficient drawing/communication by only updating changed squares
|
||||
- growing plants
|
||||
- monster/object spawners
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
|
||||
class Alignment:
|
||||
|
||||
def __init__(self, faction):
|
||||
self.faction = faction
|
||||
|
||||
def attach(self, obj, roomData):
|
||||
self.owner = obj
|
||||
self.roomData = roomData
|
||||
roomData.addTarget(obj)
|
||||
|
||||
def getFaction(self):
|
||||
return self.faction
|
||||
|
||||
def isEnemy(self, obj):
|
||||
alignment = obj.getComponent("alignment")
|
||||
if not alignment:
|
||||
return False
|
||||
return self.faction.isEnemy(alignment.getFaction())
|
||||
|
||||
def remove(self):
|
||||
self.roomData.removeTarget(self.owner)
|
|
@ -12,11 +12,11 @@ class Fighter:
|
|||
self.slowness = slowness
|
||||
self.canAttack = True
|
||||
|
||||
def attach(self, owner, events):
|
||||
def attach(self, owner, roomData):
|
||||
self.owner = owner
|
||||
self.fightEvent = events["fight"]
|
||||
self.updateEvent = events["update"]
|
||||
self.timeout = timeout.Timeout(events["update"], self.slowness)
|
||||
self.fightEvent = roomData.getEvent("fight")
|
||||
self.updateEvent = roomData.getEvent("update")
|
||||
self.timeout = timeout.Timeout(roomData.getEvent("update"), self.slowness)
|
||||
|
||||
def damage(self, damage, attacker):
|
||||
self.health -= damage
|
||||
|
|
|
@ -15,14 +15,14 @@ class Growing:
|
|||
self.nextKwargs = nextKwargs
|
||||
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
self.owner = obj
|
||||
self.roomEvents = events
|
||||
self.timeout = timeout.Timeout(events["update"], self.duration - self.stepsPassed, self.grow)
|
||||
self.roomData = roomData
|
||||
self.timeout = timeout.Timeout(roomData.getEvent("update"), self.duration - self.stepsPassed, self.grow)
|
||||
|
||||
def grow(self, to):
|
||||
|
||||
obj = gameobjects.makeEntity(self.nextStage, self.roomEvents, *self.nextArgs, **self.nextKwargs)
|
||||
obj = gameobjects.makeEntity(self.nextStage, self.roomData, *self.nextArgs, **self.nextKwargs)
|
||||
obj.place(self.owner.getGround())
|
||||
|
||||
self.owner.trigger("grow", obj)
|
||||
|
|
|
@ -10,7 +10,7 @@ class Healing:
|
|||
self.amount = amount
|
||||
self.delay = 0
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
|
||||
if not obj.getComponent("fighter"):
|
||||
# todo: better exception
|
||||
|
@ -18,7 +18,7 @@ class Healing:
|
|||
|
||||
self.fighter = obj.getComponent("fighter")
|
||||
|
||||
self.timeEvent = events["update"]
|
||||
self.timeEvent = roomData.getEvent("update")
|
||||
|
||||
obj.addListener(self.onObjEvent)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class InputController:
|
|||
def __init__(self):
|
||||
self.actions = queue.Queue()
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
self.owner = obj
|
||||
|
||||
for dep in {"inventory", "move", "fighter", "alignment"}:
|
||||
|
@ -16,7 +16,7 @@ class InputController:
|
|||
|
||||
setattr(self, dep, obj.getComponent(dep))
|
||||
|
||||
self.controlEvent = events["control"]
|
||||
self.controlEvent = roomData.getEvent("control")
|
||||
self.controlEvent.addListener(self.control)
|
||||
|
||||
def addAction(self, action):
|
||||
|
|
|
@ -8,10 +8,10 @@ class MonsterAi:
|
|||
|
||||
def __init__(self, viewDist, moveChance=1):
|
||||
self.moveChance = moveChance
|
||||
self.viewdist = viewDist
|
||||
self.viewDist = viewDist
|
||||
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
self.owner = obj
|
||||
|
||||
for dep in {"move", "fighter", "alignment"}:
|
||||
|
@ -21,27 +21,28 @@ class MonsterAi:
|
|||
|
||||
setattr(self, dep, obj.getComponent(dep))
|
||||
|
||||
self.controlEvent = events["control"]
|
||||
self.controlEvent = roomData.getEvent("control")
|
||||
self.controlEvent.addListener(self.control)
|
||||
self.roomData = roomData
|
||||
|
||||
|
||||
def control(self):
|
||||
# todo: getObjsInRange is very slow, O(n^2).
|
||||
# better select target from a list of all entities in the room that have alignment instead
|
||||
for obj in pathfinding.getObjsInRange(self.owner, self.viewdist):
|
||||
if self.alignment.isEnemy(obj):
|
||||
# this is now the closest enemy
|
||||
|
||||
if pathfinding.distanceBetween(self.owner, obj) < 2:
|
||||
self.fighter.attack(obj)
|
||||
else:
|
||||
self.move.move(pathfinding.stepTo(self.owner, obj))
|
||||
break
|
||||
closestDistance = self.viewDist + 1
|
||||
closest = None
|
||||
for obj in self.roomData.getTargets():
|
||||
distance = pathfinding.distanceBetween(self.owner, obj)
|
||||
if self.alignment.isEnemy(obj) and distance < closestDistance:
|
||||
closestDistance = distance
|
||||
closest = obj
|
||||
if closest:
|
||||
if pathfinding.distanceBetween(self.owner, closest) <= 1:
|
||||
self.fighter.attack(closest)
|
||||
else:
|
||||
self.move.move(pathfinding.stepTo(self.owner, closest))
|
||||
else:
|
||||
if random.random() < self.moveChance:
|
||||
direction = random.choice(["north", "south", "east", "west"])
|
||||
self.move.move(direction)
|
||||
|
||||
|
||||
def remove(self):
|
||||
self.controlEvent.removeListener(self.control)
|
||||
|
|
|
@ -9,10 +9,10 @@ class Move:
|
|||
self.canMove = False
|
||||
self.timeout = None
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
self.owner = obj
|
||||
self.moveEvent = events["move"]
|
||||
self.updateEvent = events["update"]
|
||||
self.moveEvent = roomData.getEvent("move")
|
||||
self.updateEvent = roomData.getEvent("update")
|
||||
self.canMove = True
|
||||
|
||||
|
||||
|
|
|
@ -7,14 +7,14 @@ class RandomWalkController:
|
|||
self.moveChance = moveChance
|
||||
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
self.owner = obj
|
||||
|
||||
if not obj.getComponent("move"):
|
||||
# todo: better exception
|
||||
raise Exception("Controller needs object with move component")
|
||||
|
||||
self.controlEvent = events["control"]
|
||||
self.controlEvent = roomData.getEvent("control")
|
||||
self.controlEvent.addListener(self.control)
|
||||
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ class Spawner:
|
|||
self.objectKwargs = objectKwargs
|
||||
self.timeouts = set()
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
|
||||
self.owner = obj
|
||||
self.roomEvents = events
|
||||
self.updateEvent = events["update"]
|
||||
self.roomData = roomData
|
||||
self.updateEvent = roomData.getEvent("update")
|
||||
|
||||
for i in range(self.amount):
|
||||
self.goSpawn()
|
||||
|
@ -27,7 +27,7 @@ class Spawner:
|
|||
self.timeouts.add(to)
|
||||
|
||||
def spawn(self, to):
|
||||
obj = gameobjects.makeEntity(self.objectType, self.roomEvents, *self.objectArgs, **self.objectKwargs)
|
||||
obj = gameobjects.makeEntity(self.objectType, self.roomData, *self.objectArgs, **self.objectKwargs)
|
||||
obj.place(self.owner.getGround())
|
||||
self.spawned.add(obj)
|
||||
self.timeouts.remove(to)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class Trap:
|
||||
|
||||
|
||||
def attach(self, obj, events):
|
||||
def attach(self, obj, roomData):
|
||||
|
||||
if not obj.getComponent("fighter"):
|
||||
# todo: better exception
|
||||
|
|
|
@ -6,17 +6,11 @@ class Faction:
|
|||
|
||||
def hates(self, faction):
|
||||
self.enemies.add(faction)
|
||||
if not faction.isEnemyFaction(self):
|
||||
if not faction.isEnemy(self):
|
||||
faction.hates(self)
|
||||
|
||||
def isEnemyFaction(self, faction):
|
||||
def isEnemy(self, faction):
|
||||
return faction in self.enemies
|
||||
|
||||
def isEnemy(self, obj):
|
||||
faction = obj.getComponent("alignment")
|
||||
if not faction:
|
||||
return False
|
||||
return self.isEnemyFaction(faction)
|
||||
|
||||
|
||||
NEUTRAL = Faction() # doesn't hate anyone
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import random
|
||||
import entity
|
||||
import faction
|
||||
from entity import Entity
|
||||
from components.item import Item
|
||||
from components.randomwalkcontroller import RandomWalkController
|
||||
|
@ -9,10 +10,10 @@ from components.move import Move
|
|||
from components.portal import Portal
|
||||
from components.trap import Trap
|
||||
from components.fighter import Fighter
|
||||
from components.faction import NEUTRAL, GOOD, EVIL
|
||||
from components.monsterai import MonsterAi
|
||||
from components.spawner import Spawner
|
||||
from components.grow import Growing
|
||||
from components.alignment import Alignment
|
||||
|
||||
|
||||
""" This module contains factory functions for many placable entities, and a make function to call a factory by a string name """
|
||||
|
@ -20,72 +21,72 @@ from components.grow import Growing
|
|||
entities = {}
|
||||
|
||||
|
||||
def makeWall(roomEvents):
|
||||
return Entity(roomEvents, sprite="wall", height=2, solid=True)
|
||||
def makeWall(roomData):
|
||||
return Entity(roomData, sprite="wall", height=2, solid=True)
|
||||
entities["wall"] = makeWall
|
||||
|
||||
def makeRock(roomEvents):
|
||||
return Entity(roomEvents, sprite="rock", height=10, solid=True)
|
||||
def makeRock(roomData):
|
||||
return Entity(roomData, sprite="rock", height=10, solid=True)
|
||||
entities["rock"] = makeRock
|
||||
|
||||
def makeTree(roomEvents):
|
||||
return Entity(roomEvents, sprite="tree", height=3, solid=True)
|
||||
def makeTree(roomData):
|
||||
return Entity(roomData, sprite="tree", height=3, solid=True)
|
||||
entities["tree"] = makeTree
|
||||
|
||||
def makeStone(roomEvents):
|
||||
return Entity(roomEvents, sprite="stone", height=0.2, components={"item": Item()})
|
||||
def makeStone(roomData):
|
||||
return Entity(roomData, sprite="stone", height=0.2, components={"item": Item()})
|
||||
entities["stone"] = makeStone
|
||||
|
||||
def makePebble(roomEvents):
|
||||
return Entity(roomEvents, sprite="pebble", height=0.2, components={"item": Item()})
|
||||
def makePebble(roomData):
|
||||
return Entity(roomData, sprite="pebble", height=0.2, components={"item": Item()})
|
||||
entities["pebble"] = makePebble
|
||||
|
||||
def makeGrass(roomEvents):
|
||||
return Entity(roomEvents, sprite=random.choice(["ground", "grass1", "grass2", "grass3"]), height=0.15)
|
||||
def makeGrass(roomData):
|
||||
return Entity(roomData, sprite=random.choice(["ground", "grass1", "grass2", "grass3"]), height=0.15)
|
||||
entities["grass"] = makeGrass
|
||||
|
||||
def makeFloor(roomEvents):
|
||||
return Entity(roomEvents, sprite="floor", height=0.1)
|
||||
def makeFloor(roomData):
|
||||
return Entity(roomData, sprite="floor", height=0.1)
|
||||
entities["floor"] = makeFloor
|
||||
|
||||
def makeGround(roomEvents):
|
||||
return Entity(roomEvents, sprite="ground", height=0.1)
|
||||
def makeGround(roomData):
|
||||
return Entity(roomData, sprite="ground", height=0.1)
|
||||
entities["ground"] = makeGround
|
||||
|
||||
def makeWater(roomEvents):
|
||||
return Entity(roomEvents, sprite="water", height=0.1, solid=True)
|
||||
def makeWater(roomData):
|
||||
return Entity(roomData, sprite="water", height=0.1, solid=True)
|
||||
entities["water"] = makeWater
|
||||
|
||||
def makeRoomExit(roomEvents, destRoom, destPos=None, char="exit", size=1):
|
||||
return Entity(roomEvents, sprite=char, height=size, components={"collision": Portal(destRoom, destPos)})
|
||||
def makeRoomExit(roomData, destRoom, destPos=None, char="exit", size=1):
|
||||
return Entity(roomData, sprite=char, height=size, components={"collision": Portal(destRoom, destPos)})
|
||||
entities["roomexit"] = makeRoomExit
|
||||
|
||||
def makeRabbit(roomEvents):
|
||||
return Entity(roomEvents, sprite="rabbit", height=1, components={"move": Move(slowness=4), "controller": RandomWalkController(moveChance=0.05)})
|
||||
def makeRabbit(roomData):
|
||||
return Entity(roomData, sprite="rabbit", height=1, components={"move": Move(slowness=4), "controller": RandomWalkController(moveChance=0.05)})
|
||||
entities["rabbit"] = makeRabbit
|
||||
|
||||
def makeDummy(roomEvents):
|
||||
return Entity(roomEvents, sprite="dummy", height=1, components={"fighter": Fighter(maxHealth=20, strength=0), "alignment": EVIL})
|
||||
def makeDummy(roomData):
|
||||
return Entity(roomData, sprite="dummy", height=1, components={"fighter": Fighter(maxHealth=20, strength=0), "alignment": Alignment(faction.EVIL)})
|
||||
entities["dummy"] = makeDummy
|
||||
|
||||
def makeSpikeTrap(roomEvents):
|
||||
return Entity(roomEvents, sprite="spikes", height=1, components={"fighter": Fighter(maxHealth=25, strength=25), "collision": Trap()})
|
||||
def makeSpikeTrap(roomData):
|
||||
return Entity(roomData, sprite="spikes", height=1, components={"fighter": Fighter(maxHealth=25, strength=25), "collision": Trap()})
|
||||
entities["spiketrap"] = makeSpikeTrap
|
||||
|
||||
def makeGoblin(roomEvents):
|
||||
return Entity(roomEvents, sprite="goblin", height=1.2, components={"move": Move(slowness=4), "fighter": Fighter(maxHealth=25, strength=5, slowness=3), "alignment": EVIL, "controller": MonsterAi(viewDist=5, moveChance=0.01)})
|
||||
def makeGoblin(roomData):
|
||||
return Entity(roomData, sprite="goblin", height=1.2, components={"move": Move(slowness=4), "fighter": Fighter(maxHealth=25, strength=5, slowness=3), "alignment": Alignment(faction.EVIL), "controller": MonsterAi(viewDist=5, moveChance=0.01)})
|
||||
entities["goblin"] = makeGoblin
|
||||
|
||||
def makeGoblinSpawner(roomEvents): # I should probably generalize this...
|
||||
return Entity(roomEvents, sprite="portal", height=1, name="goblinspawner", components={"spawn": Spawner("goblin", 2, 20)})
|
||||
def makeGoblinSpawner(roomData): # I should probably generalize this...
|
||||
return Entity(roomData, sprite="portal", height=1, name="goblinspawner", components={"spawn": Spawner("goblin", 2, 20)})
|
||||
entities["goblinspawner"] = makeGoblinSpawner
|
||||
|
||||
def makeSeed(roomEvents):
|
||||
return Entity(roomEvents, sprite="seed", height=0.3, components={"grow": Growing("plant", 100)})
|
||||
def makeSeed(roomData):
|
||||
return Entity(roomData, sprite="seed", height=0.3, components={"grow": Growing("plant", 100)})
|
||||
entities["seed"] = makeSeed
|
||||
|
||||
def makePlant(roomEvents):
|
||||
return Entity(roomEvents, sprite="plant", height=1.2)
|
||||
def makePlant(roomData):
|
||||
return Entity(roomData, sprite="plant", height=1.2)
|
||||
entities["plant"] = makePlant
|
||||
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ from components.inventory import Inventory
|
|||
from components.inputcontroller import InputController
|
||||
from components.move import Move
|
||||
from components.fighter import Fighter
|
||||
from components.faction import GOOD, EVIL, NEUTRAL
|
||||
from components.healing import Healing
|
||||
from components.alignment import Alignment
|
||||
import faction
|
||||
|
||||
class Player:
|
||||
|
||||
|
@ -50,7 +51,7 @@ class Player:
|
|||
"move": Move(slowness=2),
|
||||
"controller": InputController(),
|
||||
"fighter": Fighter(self.health or self.maxHealth, 5, slowness=2),
|
||||
"alignment": GOOD,
|
||||
"alignment": Alignment(faction.GOOD),
|
||||
"heal": Healing(interval=25)
|
||||
})
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import gameobjects
|
|||
import grid
|
||||
import event
|
||||
import entity
|
||||
import roomdata
|
||||
|
||||
|
||||
class Room:
|
||||
|
@ -19,12 +20,12 @@ class Room:
|
|||
self.changedCells = {} # this probably doesn't belong in this class, but for now it will do
|
||||
# It's probably better to make the view component more elaborate
|
||||
|
||||
self.events = {
|
||||
self.roomData = roomdata.RoomData(events={
|
||||
"control": event.Event(),
|
||||
"move": event.Event(),
|
||||
"fight": event.Event(),
|
||||
"update": event.Event()
|
||||
}
|
||||
})
|
||||
|
||||
self.places = data.get("places", {})
|
||||
|
||||
|
@ -60,10 +61,10 @@ class Room:
|
|||
|
||||
'update' also has the number of steps as argument. This will be useful when room unloading becomes a thing, and when the room loads again a lot of steps have to be simulated.
|
||||
"""
|
||||
self.events["control"].trigger()
|
||||
self.events["move"].trigger()
|
||||
self.events["fight"].trigger()
|
||||
self.events["update"].trigger(1)
|
||||
self.roomData.getEvent("control").trigger()
|
||||
self.roomData.getEvent("move").trigger()
|
||||
self.roomData.getEvent("fight").trigger()
|
||||
self.roomData.getEvent("update").trigger(1)
|
||||
|
||||
def getSprite(self, pos):
|
||||
return self._getGround(pos).getTopObj().getSprite()
|
||||
|
@ -87,10 +88,10 @@ class Room:
|
|||
return None
|
||||
|
||||
def makeObject(self, objtype, *args, **kwargs):
|
||||
return gameobjects.makeEntity(objtype, self.events, *args, **kwargs)
|
||||
return gameobjects.makeEntity(objtype, self.roomData, *args, **kwargs)
|
||||
|
||||
def makeEntity(self, *args, **kwargs):
|
||||
return entity.Entity(self.events, *args, **kwargs)
|
||||
return entity.Entity(self.roomData, *args, **kwargs)
|
||||
|
||||
def addObj(self, pos, obj):
|
||||
obj.place(self.get(pos))
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
|
||||
|
||||
class RoomData:
|
||||
|
||||
""" Instances of this class represents the data about the room that is available to the entities in the room and their components
|
||||
|
||||
The Ground class does this as well, but Ground only gives local data, whereas this give data about the whole room.
|
||||
"""
|
||||
|
||||
def __init__(self, events=[]):
|
||||
|
||||
self.events = events
|
||||
self.targets = set()
|
||||
|
||||
|
||||
|
||||
def getEvent(self, name):
|
||||
return self.events[name]
|
||||
|
||||
def addTarget(self, obj):
|
||||
self.targets.add(obj)
|
||||
|
||||
def removeTarget(self, obj):
|
||||
self.targets.remove(obj)
|
||||
|
||||
def getTargets(self):
|
||||
return frozenset(self.targets)
|
Loading…
Reference in New Issue