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:
troido 2017-10-21 20:45:44 +02:00
parent fc0f9bde9d
commit 7f398ebe12
16 changed files with 140 additions and 91 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -3,7 +3,7 @@
class Trap:
def attach(self, obj, events):
def attach(self, obj, roomData):
if not obj.getComponent("fighter"):
# todo: better exception

View File

@ -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

View File

@ -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

View File

@ -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)
})

View File

@ -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))

28
server/roomdata.py Normal file
View File

@ -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)