Compare commits
45 Commits
Author | SHA1 | Date |
---|---|---|
troido | 9473502e6e | |
troido | ab720679aa | |
troido | 156e0ebff7 | |
troido | abf7200d58 | |
troido | 062cedb67b | |
troido | bffba2abb7 | |
troido | daa712fedb | |
troido | 8d0f2f06b2 | |
troido | e98118d958 | |
troido | a22baed6e1 | |
troido | c1eec37c4a | |
troido | 265ad53c18 | |
troido | deb5385996 | |
troido | 480688d36e | |
troido | 5369da7c53 | |
troido | cc741be4e8 | |
troido | b987871f27 | |
troido | 0ebdd174dd | |
troido | da00624e44 | |
troido | 04b37ca667 | |
troido | 7672b945c0 | |
troido | 6c854a3db9 | |
troido | 7af47c6c67 | |
troido | f2be6242cc | |
troido | 41232938a0 | |
troido | d12cd74e64 | |
troido | 7341216f52 | |
troido | 820428ba74 | |
troido | 3812585864 | |
troido | bfcb533880 | |
troido | 1c450ca845 | |
troido | f6c6c8f000 | |
troido | dc9ae999d6 | |
troido | 64ce165878 | |
troido | 2976b5c038 | |
troido | 7a68881489 | |
troido | cd8d7321e2 | |
troido | c28a63641d | |
troido | 81b5f6b226 | |
troido | 00b3e13d0d | |
troido | 58e77aac97 | |
troido | b7db0ab600 | |
troido | 2267582f11 | |
troido | 060adac899 | |
troido | 187306c081 |
|
@ -29,7 +29,7 @@ class CommandHandler:
|
|||
"selectitem": self.selectItem,
|
||||
"inputwithselected": self.actWithSelected,
|
||||
"use": self.useSelected,
|
||||
"unuse": self.unUseSelected,
|
||||
"drop": self.dropSelected,
|
||||
"take": self.takeSelected,
|
||||
"eval": self.eval,
|
||||
"exec": self.exec,
|
||||
|
@ -74,7 +74,7 @@ class CommandHandler:
|
|||
self.input(["say", text])
|
||||
|
||||
def pick(self, option):
|
||||
self.input(["pick", option])
|
||||
self.input(["interact", [None, "north", "south", "east", "west"], option])
|
||||
|
||||
def chat(self, text):
|
||||
self.client.sendChat( text)
|
||||
|
@ -103,20 +103,15 @@ class CommandHandler:
|
|||
menu = self.client.display.getSelectedMenu()
|
||||
selected = self.client.display.getSelectedItem(menu)
|
||||
if menu in ("inventory", "equipment"):
|
||||
action = "use"
|
||||
self.input(["use", menu, selected])
|
||||
elif menu == "ground":
|
||||
action = "interact",
|
||||
else:
|
||||
return
|
||||
self.input([action, selected])
|
||||
self.input(["interact", selected])
|
||||
|
||||
def unUseSelected(self):
|
||||
def dropSelected(self):
|
||||
menu = self.client.display.getSelectedMenu()
|
||||
selected = self.client.display.getSelectedItem(menu)
|
||||
if menu == "inventory":
|
||||
action = "drop"
|
||||
elif menu == "equipment":
|
||||
action = "unequip"
|
||||
else:
|
||||
return
|
||||
self.input([action, selected])
|
||||
|
|
|
@ -88,7 +88,7 @@ class Client:
|
|||
if error == "invalidname":
|
||||
self.close("Invalid name error: "+ str(message.description))
|
||||
return
|
||||
self.log(message.errtype + ": " + message.description)
|
||||
self.log(message.errType + ": " + message.description)
|
||||
elif isinstance(message, messages.MessageMessage):
|
||||
self.log(message.text, message.type)
|
||||
elif isinstance(message, messages.WorldMessage):
|
||||
|
|
|
@ -87,7 +87,7 @@ class InputHandler:
|
|||
elif key == "^I": # tab
|
||||
# return to game but keep entered string
|
||||
self.typing = False
|
||||
elif key.isprintable():
|
||||
elif key.isprintable() and len(key) == 1:
|
||||
self.string = self.string[:self.cursor] + key + self.string[self.cursor:]
|
||||
self.cursor += len(key)
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
"w": null,
|
||||
"W": null,
|
||||
"q": ["move", "west"],
|
||||
"a": ["unuse"],
|
||||
"a": ["drop"],
|
||||
"A": ["take"],
|
||||
"Z": ["input", ["attack", "north"]],
|
||||
"Q": ["input", ["attack", "west"]]
|
||||
"Z": ["input", ["attack", ["north"]]],
|
||||
"Q": ["input", ["attack", ["west"]]]
|
||||
},
|
||||
"help": "Controls:\n aqsd or arrows:\n Move around\n e: Grab\n q: Drop/unequip\n selected\n r: Interact\n f: Attack\n t: Chat\n E: Use selected\n A: Take selected\n xc: select item\n vb: select menu\n ctrl-c: close client"
|
||||
"help": "Controls:\n aqsd or arrows: Move around\n e: Grab\n q: Drop selected\n r: Interact\n f: Attack\n t: Chat\n E: Use selected\n A: Take selected\n xc: select item\n vb: select menu\n ctrl-c: close client"
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
"j": ["move", "south"],
|
||||
"l": ["move", "east"],
|
||||
"h": ["move", "west"],
|
||||
"e": ["input", ["take"]],
|
||||
"q": ["unuse"],
|
||||
"e": ["input", ["take", null]],
|
||||
"q": ["drop"],
|
||||
"Q": ["take"],
|
||||
"E": ["use"],
|
||||
"R": ["input", ["interact"]],
|
||||
"R": ["input", ["interact", [null]]],
|
||||
"r": ["input", ["interact", [null, "north", "south", "east", "west"]]],
|
||||
"x": ["selectitem", -1, true, true],
|
||||
"c": ["selectitem", 1, true, true],
|
||||
|
@ -27,16 +27,16 @@
|
|||
"/": ["selectwidget", -1, true, true],
|
||||
"*": ["selectwidget", 1, true, true],
|
||||
"f": ["input", ["attack", [null, "north", "south", "east", "west"]]],
|
||||
"F": ["input", ["attack"]],
|
||||
"W": ["input", ["attack", "north"]],
|
||||
"S": ["input", ["attack", "south"]],
|
||||
"D": ["input", ["attack", "east"]],
|
||||
"A": ["input", ["attack", "west"]],
|
||||
"F": ["input", ["attack", [null]]],
|
||||
"W": ["input", ["attack", ["north"]]],
|
||||
"S": ["input", ["attack", ["south"]]],
|
||||
"D": ["input", ["attack", ["east"]]],
|
||||
"A": ["input", ["attack", ["west"]]],
|
||||
"t": ["runinput"],
|
||||
"enter": ["runinput"],
|
||||
"pageup": ["scrollchat", 1],
|
||||
"pagedown": ["scrollchat", -1],
|
||||
"/": ["runinput", "/"]
|
||||
},
|
||||
"help": " Controls:\nwasd or arrows: Move around\ne: Grab\nq: Drop/unequip selected\nr: Interact\nf: Attack\nt: Chat\nE: Use selected\nxc: select item\nvb: select menu\nctrl-c: close client\nPgUp/PgDn: scroll chat"
|
||||
"help": " Controls:\nwasd or arrows: Move around\ne: Grab\nq: Drop selected\nr: Interact\nf: Attack\nt: Chat\nE: Use selected\nxc: select item\nvb: select menu\nctrl-c: close client\nPgUp/PgDn: scroll chat"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"height": 11,
|
||||
"spawn": [17, 8],
|
||||
"places": {
|
||||
"stairup": [18, 8]
|
||||
"stairup": [17, 8]
|
||||
},
|
||||
|
||||
"grid": [
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"width": 64,
|
||||
"height": 64,
|
||||
"spawn": [10, 15],
|
||||
"spawn": [15, 30],
|
||||
"places": {
|
||||
"stairdown": [38, 33],
|
||||
"caveentrance": [31, 51],
|
||||
"stairdown": [37, 33],
|
||||
"caveentrance": [31, 50],
|
||||
"right": [62, 10],
|
||||
"cavebridge": [1, 60]
|
||||
},
|
||||
|
@ -43,17 +43,17 @@
|
|||
",~~~~~,,,,,'..'',,,,,,,,,,,,,,,,,,,,,,,,,,'''''''',,,,,,,,,,,,'1",
|
||||
",~~~~~,,,,,''.'',,,,,,,,,,,,,,,,,,,,,,,,,,'''T'''',,,,,,,,,,,,'1",
|
||||
",~~~~~,,,,,'..''''''#####################''''''''',,,,,,,,,,,,'1",
|
||||
",~~~~~,,,,,''..'''''#+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
",~~~~~,,,,,''..'''BB#+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
",~~~~~'''''''..'''.'#+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~''''''''''..'''#+++++++++++++++++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~''''''''.'.''..D+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~'''''..''.'....D+++++++++++++#######''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~''''''.'..'....#+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~'''5''''''../!]#+++++++++++++++++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~'''6''''.'.''..D+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~'''7'..''.'....D+++++++++++++#######''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~'888''.'..'....#+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,'.'#d.....#+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,'.'#..d..^#+++++++++++++++++>+#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,8.'#..d..^#+++++++++++++++++>+#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,'.'########+++++++++++++#+++++#''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,'.''''''''#####################''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,'.'''''''''''''''''''''''''''''''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~,,,,,,8.'''''''''''''''''''''''''''''''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~,,,,,'.'''''''''''''''''''''''''''''''''''''',,,,,,,,,,,,'1",
|
||||
"~~~~~,,,,,'.'''''''''''''''''''''''''''''''''''''''''''''''''''1",
|
||||
"~~~~~~,,,,'....................................................1",
|
||||
|
@ -142,6 +142,11 @@
|
|||
"args": ["tutorial","tunnelout"]
|
||||
}, "floor"],
|
||||
"s": ["grass", "trader"],
|
||||
"B": ["grass", "freeland", "builtwall"],
|
||||
"5": ["grass", "plantedradishseed"],
|
||||
"6": ["grass", "plantedradishseedling"],
|
||||
"7": ["grass", "youngradishplant"],
|
||||
"8": ["grass", "radishplant"],
|
||||
" ": []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"height": 44,
|
||||
"spawn": [32, 4],
|
||||
"places": {
|
||||
"stairup": [32, 3]
|
||||
"stairup": [32, 4]
|
||||
},
|
||||
"grid": [
|
||||
" ",
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
from .alignment import Alignment
|
||||
from .build import Build
|
||||
from .change import Change
|
||||
from .customserializer import CustomSerializer
|
||||
from .equippable import Equippable
|
||||
from .fighter import Fighter
|
||||
from .food import Food
|
||||
from .grow import Growing
|
||||
from .harvest import Harvest
|
||||
from .item import Item
|
||||
from .loot import Loot
|
||||
from .move import Move
|
||||
from .monsterai import MonsterAi
|
||||
from .optionmenu import OptionMenu
|
||||
from .portal import Portal
|
||||
from .randomwalkcontroller import RandomWalkController
|
||||
from .select import Select
|
||||
from .selectable import Selectable
|
||||
from .spawner import Spawner
|
||||
from .staticserializer import StaticSerializer
|
||||
from .trap import Trap
|
||||
from .volatile import Volatile
|
||||
from .weather import Weather
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
from .. import faction
|
||||
from .component import Component
|
||||
|
||||
class Alignment(Component):
|
||||
|
||||
def __init__(self, faction):
|
||||
self.faction = faction
|
||||
|
||||
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 __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
self.faction = faction.factions[self.faction.getName()]
|
||||
|
||||
def toJSON(self):
|
||||
return self.faction.getName()
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, facName):
|
||||
return cls(faction.factions[facName])
|
|
@ -1,43 +0,0 @@
|
|||
|
||||
from .. import gameobjects
|
||||
from .component import Component
|
||||
|
||||
class Build(Component):
|
||||
""" item type for item that can be placed on the map to become something more static (like buildable walls or crops)"""
|
||||
|
||||
def __init__(self, objType, objArgs=(), objKwargs=None, flagsNeeded=frozenset(), blockingFlags=frozenset()):
|
||||
if objKwargs is None:
|
||||
objKwargs = {}
|
||||
self.buildType = objType
|
||||
self.buildArgs = objArgs
|
||||
self.buildKwargs = objKwargs
|
||||
self.flagsNeeded = set(flagsNeeded)
|
||||
self.blockingFlags = set(blockingFlags)
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, obj, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
|
||||
|
||||
def use(self, user):
|
||||
groundFlags = user.getGround().getFlags()
|
||||
if not self.flagsNeeded <= groundFlags or groundFlags & self.blockingFlags: # <= means subset when applied on sets
|
||||
# groundFlags must contain all of self.flagsNeeded, and none of self.blockingFlags
|
||||
return
|
||||
roomData = user.getRoomData()
|
||||
obj = gameobjects.makeEntity(self.buildType, roomData, *self.buildArgs, preserve=True, **self.buildKwargs)
|
||||
obj.place(user.getGround())
|
||||
self.owner.trigger("drop")
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"objType": self.buildType,
|
||||
"objArgs": self.buildArgs,
|
||||
"objKwargs": self.buildKwargs,
|
||||
"flagsNeeded": list(self.flagsNeeded),
|
||||
"blockingFlags": list(self.blockingFlags)
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
from .. import gameobjects
|
||||
|
||||
class Change(Component):
|
||||
""" Objects that change type on interaction.
|
||||
|
||||
This could probably also be implemented with a combination of harvest and loot,
|
||||
but this seemed better."""
|
||||
|
||||
def __init__(self, into, permitted=None, nextArgs=None, nextKwargs=None):
|
||||
|
||||
self.into = into
|
||||
self.nextArgs = nextArgs or []
|
||||
self.nextKwargs = nextKwargs or {}
|
||||
|
||||
self.permitted = permitted
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
|
||||
def interact(self, other):
|
||||
if self.permitted is not None and other.getName() not in self.permitted:
|
||||
return
|
||||
obj = gameobjects.makeEntity(self.into, self.roomData, *self.nextArgs, preserve=self.owner.isPreserved(), **self.nextKwargs)
|
||||
|
||||
obj.place(self.owner.getGround())
|
||||
self.owner.remove()
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"into": self.into,
|
||||
"permitted": self.permitted,
|
||||
"nextArgs": self.nextArgs,
|
||||
"nextKwargs": self.nextKwargs
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
|
||||
|
||||
class Component:
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
pass
|
||||
|
||||
|
||||
def remove(self):
|
||||
pass
|
||||
|
||||
def toJSON(self):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, data=None):
|
||||
if data is None:
|
||||
return cls()
|
||||
return cls(**data)
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
|
||||
from .component import Component
|
||||
|
||||
class CustomSerializer(Component):
|
||||
|
||||
def __init__(self, fn):
|
||||
self.fn = fn
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def serialize(self):
|
||||
return self.fn(self.owner)
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
from .. import gameobjects
|
||||
|
||||
|
||||
class Equipment(Component):
|
||||
|
||||
|
||||
def __init__(self, slots=None):
|
||||
if slots is None:
|
||||
slots = {}
|
||||
self.slots = {key: None for key in slots}
|
||||
self.owner = None
|
||||
for slot, item in slots.items():
|
||||
self.equip(slot, item)
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def getBonus(self, skill):
|
||||
return sum(item.getComponent("item").getStat(skill) for item in self.slots.values() if item)
|
||||
|
||||
def equip(self, slot, item):
|
||||
if slot in self.slots:
|
||||
oldItem = self.slots[slot]
|
||||
if oldItem:
|
||||
self.owner.trigger("take", oldItem)
|
||||
self.slots[slot] = item
|
||||
if self.owner:
|
||||
self.owner.trigger("equipmentchange")
|
||||
else:
|
||||
# todo: better exception
|
||||
raise Exception("No such slot")
|
||||
|
||||
def unEquip(self, slot):
|
||||
if slot in self.slots:
|
||||
self.slots[slot] = None
|
||||
if self.owner:
|
||||
self.owner.trigger("equipmentchange")
|
||||
|
||||
def getSlots(self):
|
||||
return self.slots.copy()
|
||||
|
||||
def canEquip(self, item):
|
||||
return item.getSlot() in self.slots
|
||||
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
slotName: item.serialize() if item else None
|
||||
for slotName, item in self.slots.items()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, slots):
|
||||
return cls({
|
||||
slotname: gameobjects.createEntity(item) if item else None
|
||||
for slotname, item in slots.items()
|
||||
})
|
||||
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Equippable(Component):
|
||||
""" item type for item that can be placed on the map to become something more static (like buildable walls or crops)"""
|
||||
|
||||
def __init__(self, slot, stats=None):
|
||||
if stats is None:
|
||||
stats = {}
|
||||
self.slot = slot
|
||||
self.stats = stats
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def getSlot(self):
|
||||
return self.slot
|
||||
|
||||
def use(self, user):
|
||||
equipment = user.getComponent("equipment")
|
||||
if equipment.canEquip(self):
|
||||
equipment.equip(self.slot, self.owner)
|
||||
self.owner.trigger("drop")
|
||||
|
||||
def getStat(self, stat):
|
||||
return self.stats.get(stat, 0)
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"slot": self.slot,
|
||||
"stats": self.stats
|
||||
}
|
||||
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
|
||||
from asciifarm.common import utils
|
||||
import random
|
||||
from .component import Component
|
||||
from .. import gameobjects
|
||||
from ..pathfinding import distanceBetween
|
||||
|
||||
class Fighter(Component):
|
||||
|
||||
def __init__(self, maxHealth, strength=0, slowness=1, health=None, defense=0, range=1, attackable=True):
|
||||
self.maxHealth = maxHealth
|
||||
self.health = health or maxHealth
|
||||
self.strength = strength
|
||||
self.target = None
|
||||
self.slowness = slowness
|
||||
self.attackReady = True
|
||||
self.defense = defense
|
||||
self.range = range
|
||||
self._attackable = attackable
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
self.fightEvent = roomData.getEvent("fight")
|
||||
|
||||
def damage(self, damage, attacker):
|
||||
self.health -= damage
|
||||
self.health = utils.clamp(self.health, 0, self.maxHealth)
|
||||
|
||||
# should this be it's own component? ('bleeding' for example)
|
||||
if damage > 0 and self.owner.getGround() is not None:
|
||||
obj = gameobjects.makeEntity("wound", self.roomData, 4, self.owner.getHeight() - 0.01)
|
||||
obj.place(self.owner.getGround())
|
||||
|
||||
self.owner.trigger("damage" if damage >= 0 else "heal", attacker, abs(damage))
|
||||
|
||||
if self.isDead():
|
||||
self.die(attacker)
|
||||
|
||||
def attack(self, other):
|
||||
if self.canAttack(other):
|
||||
self.target = other
|
||||
self.fightEvent.addListener(self.doAttack)
|
||||
|
||||
def doAttack(self):
|
||||
other = self.target
|
||||
if other and other.hasComponent("fighter") and self.attackReady:
|
||||
otherFighter = other.getComponent("fighter")
|
||||
if otherFighter:
|
||||
strength = self.getStrength()
|
||||
defense = otherFighter.getDefense()
|
||||
damage = random.randint(0, int(100*strength / (defense + 100)))
|
||||
otherFighter.damage(damage, self.owner)
|
||||
|
||||
self.attackReady = False
|
||||
|
||||
self.roomData.setAlarm(self.roomData.getStamp() + self.slowness, self.makeReady)
|
||||
|
||||
self.owner.trigger("attack", other, damage)
|
||||
if otherFighter.isDead():
|
||||
self.owner.trigger("kill", other)
|
||||
|
||||
self.target = None
|
||||
self.fightEvent.removeListener(self.doAttack)
|
||||
|
||||
def die(self, killer):
|
||||
|
||||
self.owner.trigger("die", killer)
|
||||
|
||||
self.owner.remove()
|
||||
|
||||
def getStrength(self):
|
||||
strength = self.strength
|
||||
if self.owner.hasComponent("equipment"):
|
||||
strength += self.owner.getComponent("equipment").getBonus("strength")
|
||||
return strength
|
||||
|
||||
|
||||
def getDefense(self):
|
||||
defense = self.defense
|
||||
if self.owner.hasComponent("equipment"):
|
||||
defense += self.owner.getComponent("equipment").getBonus("defense")
|
||||
return defense
|
||||
|
||||
def getHealth(self):
|
||||
return (self.health, self.maxHealth)
|
||||
|
||||
def healthFull(self):
|
||||
return self.health >= self.maxHealth
|
||||
|
||||
def heal(self, health, source):
|
||||
self.damage(-health, source)
|
||||
|
||||
def isDead(self):
|
||||
return self.health <= 0
|
||||
|
||||
def inRange(self, other):
|
||||
return distanceBetween(self.owner, other) <= self.range
|
||||
|
||||
def remove(self):
|
||||
self.fightEvent.removeListener(self.doAttack)
|
||||
self.owner.removeListener("roomjoin", self.roomJoin)
|
||||
|
||||
|
||||
def makeReady(self):
|
||||
self.attackReady = True
|
||||
|
||||
def attackable(self):
|
||||
return self._attackable
|
||||
|
||||
def canAttack(self, other):
|
||||
return self.inRange(other) and other.getComponent("fighter") and other.getComponent("fighter").attackable()
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"maxHealth": self.maxHealth,
|
||||
"strength": self.strength,
|
||||
"slowness": self.slowness,
|
||||
"health": self.health,
|
||||
"defense": self.defense
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Food(Component):
|
||||
|
||||
|
||||
def __init__(self, health):
|
||||
self.healing = health
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
|
||||
def use(self, user):
|
||||
fighter = user.getComponent("fighter")
|
||||
if fighter:
|
||||
fighter.heal(self.healing, self.owner)
|
||||
self.owner.trigger("drop")
|
||||
|
||||
def toJSON(self):
|
||||
return {"health": self.healing}
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
from .. import gameobjects
|
||||
import random
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Growing(Component):
|
||||
|
||||
|
||||
def __init__(self, nextStage, duration=None, targetTime=None, nextArgs=None, nextKwargs=None, stepsPassed=None):
|
||||
# stepsPassed is not used anymore, but included for backwards compatibility
|
||||
|
||||
self.nextStage = nextStage
|
||||
|
||||
# if both duration and targetTime are passed, duration is ignored
|
||||
# if both are none, the growth will never happen
|
||||
self.duration = duration
|
||||
self.targetTime = targetTime
|
||||
self.nextArgs = nextArgs or []
|
||||
self.nextKwargs = nextKwargs or {}
|
||||
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
if self.targetTime is None and self.duration:
|
||||
duration = int(random.triangular(self.duration/2, self.duration*2, self.duration))
|
||||
self.targetTime = stamp + duration
|
||||
self.roomData.setAlarm(self.targetTime, self.grow)
|
||||
|
||||
def grow(self):
|
||||
|
||||
obj = gameobjects.createEntity({"type": self.nextStage, "args": self.nextArgs, "kwargs": self.nextKwargs})
|
||||
obj.construct(self.roomData, preserve=self.owner.isPreserved(), stamp=self.targetTime)
|
||||
obj.place(self.owner.getGround())
|
||||
|
||||
self.owner.trigger("grow", obj)
|
||||
print("{} has grown into {}".format(self.owner.getName(), obj.getName()))
|
||||
|
||||
self.owner.remove()
|
||||
|
||||
def getTargetTime(self):
|
||||
return self.targetTime
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"nextStage": self.nextStage,
|
||||
"duration": self.duration,
|
||||
"targetTime": self.targetTime,
|
||||
"nextArgs": self.nextArgs,
|
||||
"nextKwargs": self.nextKwargs
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Harvest(Component):
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def interact(self, obj):
|
||||
self.owner.trigger("die") # loot component will register this
|
||||
self.owner.remove()
|
|
@ -1,53 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Healing(Component):
|
||||
|
||||
""" A component to automatically heal its entity over time"""
|
||||
|
||||
def __init__(self, interval, amount=1):
|
||||
""" interval is the number of steps until next healing, amount is the amount of health that gets added in a healing """
|
||||
self.interval = interval
|
||||
self.amount = amount
|
||||
self.delay = 0
|
||||
self.isHealing = False
|
||||
|
||||
def attach(self, obj):
|
||||
|
||||
if not obj.getComponent("fighter"):
|
||||
# todo: better exception
|
||||
raise Exception("Healing Component needs object with fighter component")
|
||||
|
||||
self.fighter = obj.getComponent("fighter")
|
||||
obj.addListener("damage", self.onDamage)
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
self.startHealing(stamp)
|
||||
|
||||
def onDamage(self, o, *data):
|
||||
self.startHealing()
|
||||
|
||||
def startHealing(self, start=None):
|
||||
""" start healing if it is not happening already """
|
||||
if not self.isHealing and not self.fighter.healthFull():
|
||||
if start is None:
|
||||
start = self.roomData.getStamp()
|
||||
self.roomData.setAlarm(start + self.interval, self.heal)
|
||||
self.isHealing = True
|
||||
|
||||
def heal(self):
|
||||
if self.fighter.healthFull():
|
||||
return
|
||||
self.fighter.heal(self.amount, None)
|
||||
self.isHealing = False
|
||||
self.startHealing()
|
||||
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"interval": self.interval,
|
||||
"amount": self.amount
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class HomePortal(Component):
|
||||
|
||||
def __init__(self, roomName="*home:{}"):
|
||||
self.roomName = roomName
|
||||
|
||||
def attach(self, obj):
|
||||
obj.addListener("objectenter", self.onEnter)
|
||||
self.owner = obj
|
||||
|
||||
|
||||
def onEnter(self, owner, obj=None, *data):
|
||||
|
||||
obj.trigger("changeroom", self.destRoom, dest)
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"destRoom": self.destRoom,
|
||||
"destPos": self.origin,
|
||||
"mask": self.mask
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class InputController(Component):
|
||||
|
||||
def __init__(self):
|
||||
self.action = None
|
||||
|
||||
self.handlers = {
|
||||
"move": self.do_move,
|
||||
"take": self.do_take,
|
||||
"drop": self.do_drop,
|
||||
"use": self.do_use,
|
||||
"unequip": self.do_unequip,
|
||||
"interact": self.do_interact,
|
||||
"attack": self.do_attack,
|
||||
"say": self.do_say,
|
||||
"pick": self.do_pick
|
||||
}
|
||||
self.target = None
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
for dep in {"inventory", "move", "fighter", "alignment", "equipment", "select"}:
|
||||
if not obj.getComponent(dep):
|
||||
# todo: better exception
|
||||
raise Exception("InputController needs object with " + dep + " component")
|
||||
|
||||
setattr(self, dep, obj.getComponent(dep))
|
||||
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
obj.addListener("damage", self.retaliate)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
self.controlEvent = roomData.getEvent("control")
|
||||
self.controlEvent.addListener(self.control)
|
||||
|
||||
def setAction(self, action):
|
||||
self.action = action
|
||||
|
||||
|
||||
def control(self):
|
||||
action = self.action
|
||||
if action:
|
||||
self.target = None
|
||||
self.action = None
|
||||
if action is not None:
|
||||
self.executeAction(action)
|
||||
if self.target:
|
||||
self.fighter.attack(self.target)
|
||||
|
||||
def executeAction(self, action):
|
||||
|
||||
kind = action[0]
|
||||
if len(action) > 1:
|
||||
arg = action[1]
|
||||
else:
|
||||
arg = None
|
||||
try:
|
||||
handler = self.handlers.get(kind)
|
||||
except TypeError:
|
||||
handler = None
|
||||
if handler is None:
|
||||
print("invalid action", action)
|
||||
return
|
||||
handler(arg)
|
||||
|
||||
def do_move(self, direction):
|
||||
if direction not in {"north", "south", "east", "west"}:
|
||||
return
|
||||
self.move.move(direction)
|
||||
|
||||
def do_take(self, rank):
|
||||
objects = self.owner.getNearObjects()
|
||||
if rank is not None:
|
||||
if rank not in range(len(objects)):
|
||||
return
|
||||
objects = [objects[rank]]
|
||||
for obj in objects:
|
||||
if obj.getComponent("item") is not None and self.inventory.canAdd(obj):
|
||||
self.owner.trigger("take", obj)
|
||||
obj.remove()
|
||||
break
|
||||
|
||||
def do_drop(self, rank):
|
||||
items = self.inventory.getItems()
|
||||
if rank is None:
|
||||
rank = 0
|
||||
if rank not in range(len(items)):
|
||||
return False
|
||||
obj = items[rank]
|
||||
self.inventory.drop(obj)
|
||||
obj.construct(self.roomData, preserve=True)
|
||||
obj.place(self.owner.getGround())
|
||||
return True
|
||||
|
||||
def do_use(self, rank):
|
||||
items = self.inventory.getItems()
|
||||
if rank is None:
|
||||
rank = 0
|
||||
if rank not in range(len(items)):
|
||||
return
|
||||
obj = items[rank]
|
||||
obj.getComponent("item").use(self.owner)
|
||||
|
||||
def do_unequip(self, rank):
|
||||
slots = sorted(self.equipment.getSlots().items())
|
||||
if rank is not None:
|
||||
if rank not in range(len(slots)):
|
||||
return
|
||||
slots = [slots[rank]]
|
||||
for (slot, item) in slots:
|
||||
if item is not None and self.inventory.canAdd(item):
|
||||
self.equipment.unEquip(slot)
|
||||
self.owner.trigger("take", item)
|
||||
|
||||
def do_interact(self, directions):
|
||||
objects = self._getNearbyObjects(directions)
|
||||
for obj in objects:
|
||||
if obj.getComponent("interact") is not None:
|
||||
obj.getComponent("interact").interact(self.owner)
|
||||
break
|
||||
|
||||
def do_attack(self, directions):
|
||||
objects = self._getNearbyObjects(directions)
|
||||
if self.target in objects:
|
||||
objects = {self.target}
|
||||
for obj in objects:
|
||||
if self.fighter.canAttack(obj) and self.alignment.isEnemy(obj):
|
||||
self.fighter.attack(obj)
|
||||
self.target = obj
|
||||
break
|
||||
|
||||
def do_say(self, text):
|
||||
if type(text) != str:
|
||||
return
|
||||
self.roomData.getEvent("sound").trigger(self.owner, text)
|
||||
|
||||
def do_pick(self, option):
|
||||
selected = self.select.getSelected()
|
||||
if selected is None:
|
||||
return
|
||||
optionmenu = selected.getComponent("options")
|
||||
if optionmenu is None:
|
||||
return
|
||||
optionmenu.choose(option, self.owner)
|
||||
|
||||
def _getNearbyObjects(self, directions):
|
||||
nearPlaces = self.owner.getGround().getNeighbours()
|
||||
if not isinstance(directions, list):
|
||||
directions = [directions]
|
||||
objects = []
|
||||
for direction in directions:
|
||||
if direction is None:
|
||||
objects += self.owner.getNearObjects()
|
||||
elif isinstance(direction, str) and direction in nearPlaces:
|
||||
objects += nearPlaces[direction].getObjs()
|
||||
return objects
|
||||
|
||||
|
||||
def getInteractions(self):
|
||||
return []
|
||||
|
||||
def remove(self):
|
||||
self.controlEvent.removeListener(self.control)
|
||||
|
||||
def retaliate(self, _self, attacker, damage):
|
||||
self.target = attacker
|
||||
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
from .. import gameobjects
|
||||
|
||||
class Inventory(Component):
|
||||
|
||||
def __init__(self, capacity, initialItems=None):
|
||||
if initialItems is None:
|
||||
initialItems = []
|
||||
self.capacity = capacity
|
||||
self.items = []
|
||||
self.owner = None
|
||||
for item in initialItems[::-1]:
|
||||
if item:
|
||||
self.add(item)
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("take", self.onTake)
|
||||
|
||||
def canAdd(self, item):
|
||||
return len(self.items) < self.capacity
|
||||
|
||||
def canAddAll(self, items):
|
||||
return len(self.items) + len(items) <= self.capacity
|
||||
|
||||
def add(self, item):
|
||||
self.items.insert(0, item)
|
||||
item.addListener("drop", self.onDrop)
|
||||
if self.owner:
|
||||
self.owner.trigger("inventorychange")
|
||||
|
||||
def drop(self, item):
|
||||
if self.has(item):
|
||||
self.items.remove(item)
|
||||
if self.owner:
|
||||
self.owner.trigger("inventorychange")
|
||||
|
||||
def getItems(self):
|
||||
return list(self.items)
|
||||
|
||||
def has(self, item):
|
||||
return item in self.items
|
||||
|
||||
def onDrop(self, item, *data):
|
||||
self.drop(item)
|
||||
|
||||
def onTake(self, o, item=None, *data):
|
||||
self.add(item)
|
||||
|
||||
def remove(self):
|
||||
self.owner = None
|
||||
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"capacity": self.capacity,
|
||||
"items": [item.serialize() for item in self.items]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, data):
|
||||
obj = cls(data["capacity"], [gameobjects.createEntity(item) for item in data["items"]])
|
||||
return obj
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Item(Component):
|
||||
|
||||
def use(self, user):
|
||||
pass
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Listen(Component):
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
|
||||
self.soundEvent = roomData.getEvent("sound")
|
||||
self.soundEvent.addListener(self.notify)
|
||||
self.roomData = roomData
|
||||
|
||||
|
||||
def notify(self, source, text):
|
||||
self.owner.trigger("sound", source, text)
|
||||
|
||||
def remove(self):
|
||||
self.soundEvent.removeListener(self.notify)
|
|
@ -1,41 +0,0 @@
|
|||
import random
|
||||
from .. import gameobjects
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Loot(Component):
|
||||
""" entities that have this component will drop loot on death """
|
||||
|
||||
def __init__(self, items):
|
||||
""" Items should be a list of tuples where the first element is the item name, and the second element the chance that that item gets dropped """
|
||||
|
||||
self.items = items
|
||||
|
||||
def attach(self, obj):
|
||||
|
||||
self.owner = obj
|
||||
obj.addListener("die", self.dropLoot)
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
|
||||
def dropLoot(self, obj, *data):
|
||||
for itemData in self.items:
|
||||
item = itemData[0]
|
||||
chance = 1
|
||||
args = []
|
||||
kwargs = {}
|
||||
if len(itemData) > 1:
|
||||
chance = itemData[1]
|
||||
if len(itemData) > 2:
|
||||
args = itemData[2]
|
||||
if len(itemData) > 3:
|
||||
kwargs = itemData[3]
|
||||
|
||||
if chance > random.random():
|
||||
obj = gameobjects.makeEntity(item, self.roomData, *args, preserve=True, **kwargs)
|
||||
obj.place(self.owner.getGround())
|
||||
|
||||
def toJSON(self):
|
||||
return {"items": self.items}
|
|
@ -1,64 +0,0 @@
|
|||
import random
|
||||
|
||||
from .. import pathfinding
|
||||
from .component import Component
|
||||
|
||||
|
||||
class MonsterAi(Component):
|
||||
|
||||
|
||||
def __init__(self, viewDist, moveChance=1, home=None, homesickness=0.05):
|
||||
self.moveChance = moveChance
|
||||
self.viewDist = viewDist
|
||||
self.home = home # Should home be a place instead of object? that would reduce references
|
||||
self.homesickness = homesickness
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
for dep in {"move", "fighter", "alignment"}:
|
||||
if not obj.getComponent(dep):
|
||||
# todo: better exception
|
||||
raise Exception("Controller needs object with " + dep + " component")
|
||||
setattr(self, dep, obj.getComponent(dep))
|
||||
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.controlEvent = roomData.getEvent("control")
|
||||
self.controlEvent.addListener(self.control)
|
||||
self.roomData = roomData
|
||||
|
||||
|
||||
def control(self):
|
||||
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 self.fighter.inRange(closest):
|
||||
self.fighter.attack(closest)
|
||||
else:
|
||||
self.move.move(pathfinding.stepTo(self.owner, closest))
|
||||
else:
|
||||
if random.random() < self.moveChance:
|
||||
if self.home and self.home.inRoom() and random.random() < (self.homesickness * pathfinding.distanceBetween(self.owner, self.home)):
|
||||
direction = pathfinding.stepTo(self.owner, self.home)
|
||||
else:
|
||||
direction = random.choice(["north", "south", "east", "west"])
|
||||
self.move.move(direction)
|
||||
|
||||
def remove(self):
|
||||
self.controlEvent.removeListener(self.control)
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"viewDist": self.viewDist,
|
||||
"moveChance": self.moveChance,
|
||||
"homesickness": self.homesickness
|
||||
} # home is not saved now ...
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Move(Component):
|
||||
|
||||
def __init__(self, slowness=1):
|
||||
self.direction = None
|
||||
self.slowness = slowness
|
||||
self.canMove = False
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
self.moveEvent = roomData.getEvent("move")
|
||||
self.canMove = True
|
||||
|
||||
|
||||
def move(self, direction):
|
||||
self.direction = direction
|
||||
self.moveEvent.addListener(self.doMove)
|
||||
|
||||
def canMove(self, direction):
|
||||
neighbours = self.owner.getGround().getNeighbours()
|
||||
return direction in neighbours and neighbours[direction].accessible()
|
||||
|
||||
def doMove(self):
|
||||
neighbours = self.owner.getGround().getNeighbours()
|
||||
if self.direction in neighbours and self.canMove:
|
||||
newPlace = neighbours[self.direction]
|
||||
|
||||
if newPlace.accessible():
|
||||
self.owner.place(newPlace)
|
||||
self.canMove = False
|
||||
self.roomData.setAlarm(self.roomData.getStamp() + self.slowness, self.makeReady)
|
||||
self.owner.trigger("move")
|
||||
|
||||
self.direction = None
|
||||
self.moveEvent.removeListener(self.doMove)
|
||||
|
||||
def makeReady(self):
|
||||
self.canMove = True
|
||||
|
||||
def remove(self):
|
||||
self.moveEvent.removeListener(self.doMove)
|
||||
|
||||
def toJSON(self):
|
||||
return {"slowness": self.slowness}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class OptionMenu(Component):
|
||||
|
||||
def __init__(self, description, options):
|
||||
self.description = description
|
||||
self.options = {option.name: option for option in options}
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def getOptions(self):
|
||||
return list(self.options.values())
|
||||
|
||||
def choose(self, option, obj):
|
||||
if option in self.options:
|
||||
self.options[option].act(obj)
|
||||
|
||||
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Portal(Component):
|
||||
|
||||
def __init__(self, destRoom, destPos=None, mask=(False, False)):
|
||||
self.destRoom = destRoom
|
||||
self.origin = destPos
|
||||
self.mask = mask
|
||||
|
||||
def attach(self, obj):
|
||||
obj.addListener("objectenter", self.onEnter)
|
||||
self.owner = obj
|
||||
|
||||
|
||||
def onEnter(self, owner, obj, *data):
|
||||
offset = self.owner.getGround().getPos()
|
||||
if self.origin is None:
|
||||
dest = None
|
||||
elif isinstance(self.origin, str):
|
||||
dest = self.origin
|
||||
else:
|
||||
dest = tuple(
|
||||
self.origin[i] + (offset[i] if self.mask[i] else 0)
|
||||
for i in range(2)
|
||||
)
|
||||
obj.trigger("changeroom", self.destRoom, dest)
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"destRoom": self.destRoom,
|
||||
"destPos": self.origin,
|
||||
"mask": self.mask
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
import random
|
||||
|
||||
class RandomWalkController(Component):
|
||||
|
||||
def __init__(self, moveChance=1):
|
||||
self.moveChance = moveChance
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
if not obj.getComponent("move"):
|
||||
# todo: better exception
|
||||
raise Exception("Controller needs object with move component")
|
||||
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.controlEvent = roomData.getEvent("control")
|
||||
self.controlEvent.addListener(self.control)
|
||||
|
||||
|
||||
def control(self):
|
||||
|
||||
if random.random() < self.moveChance:
|
||||
direction = random.choice(["north", "south", "east", "west"])
|
||||
self.owner.getComponent("move").move(direction)
|
||||
|
||||
|
||||
def remove(self):
|
||||
self.controlEvent.removeListener(self.control)
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"moveChance": self.moveChance
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Select(Component):
|
||||
|
||||
def __init__(self):
|
||||
self.selection = None
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def select(self, obj=None):
|
||||
self.selection = obj
|
||||
self.owner.trigger("selection", obj)
|
||||
obj.trigger("selectedby", self.owner)
|
||||
|
||||
def getSelected(self):
|
||||
return self.selection
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Selectable(Component):
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
|
||||
def interact(self, obj):
|
||||
selector = obj.getComponent("select")
|
||||
if selector:
|
||||
selector.select(self.owner)
|
||||
|
||||
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
from .. import gameobjects
|
||||
import random
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Spawner(Component):
|
||||
|
||||
def __init__(self, objectType, amount=1, respawnDelay=1, setHome=False, initialSpawn=False, objectArgs=None, objectKwargs=None):
|
||||
if objectArgs is None:
|
||||
objectArgs = []
|
||||
if objectKwargs is None:
|
||||
objectKwargs = {}
|
||||
self.objectType = objectType
|
||||
self.amount = amount
|
||||
self.respawnDelay = respawnDelay
|
||||
self.spawned = set()
|
||||
self.objectArgs = objectArgs
|
||||
self.objectKwargs = objectKwargs
|
||||
self.setHome = setHome
|
||||
self.initialSpawn = initialSpawn
|
||||
|
||||
def attach(self, obj):
|
||||
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
self.updateEvent = roomData.getEvent("update")
|
||||
|
||||
for i in range(self.amount):
|
||||
if self.initialSpawn:
|
||||
self.goSpawn(stamp)
|
||||
else:
|
||||
self.goSpawn()
|
||||
|
||||
|
||||
def goSpawn(self, spawnStamp=None):
|
||||
if spawnStamp is None:
|
||||
duration = self.respawnDelay
|
||||
spawnStamp = random.triangular(duration/2, duration*2, duration) + self.roomData.getStamp()
|
||||
self.roomData.setAlarm(spawnStamp, self.spawn)
|
||||
|
||||
def spawn(self):
|
||||
objectKwargs = self.objectKwargs.copy()
|
||||
if self.setHome:
|
||||
objectKwargs["home"] = self.owner
|
||||
obj = gameobjects.makeEntity(self.objectType, self.roomData, *self.objectArgs, **objectKwargs)
|
||||
obj.place(self.owner.getGround())
|
||||
self.spawned.add(obj)
|
||||
obj.addListener("remove", self.onSpawnedRemove)
|
||||
print("{} spawned a {}".format(self.owner.getName(), self.objectType))
|
||||
|
||||
def onSpawnedRemove(self, obj, *data):
|
||||
""" handle spawned object death """
|
||||
self.spawned.remove(obj)
|
||||
obj.removeListener("remove", self.onSpawnedRemove)
|
||||
self.goSpawn()
|
||||
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"objectType": self.objectType,
|
||||
"amount": self.amount,
|
||||
"respawnDelay": self.respawnDelay,
|
||||
"setHome": self.setHome,
|
||||
"objectArgs": self.objectArgs,
|
||||
"objectKwargs": self.objectKwargs,
|
||||
"initialSpawn": self.initialSpawn
|
||||
} # it won't keep track of spawned entities. It will just spawn new entities again
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
|
||||
from .component import Component
|
||||
|
||||
class StaticSerializer(Component):
|
||||
""" This class is for static serialization.
|
||||
|
||||
If an entity has this component then the serialization will be the argument passed to the constructor instead of the toJSON of the entity and its components.
|
||||
This is useful for objects that hold no state and are not unique.
|
||||
"""
|
||||
|
||||
def __init__(self, data, *args, **kwargs):
|
||||
if isinstance(data, str) and (args or kwargs):
|
||||
self.data = {"type": data, "args": args, "kwargs": kwargs}
|
||||
else:
|
||||
self.data = data
|
||||
|
||||
def serialize(self):
|
||||
return self.data
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Target(Component):
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, obj, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
roomData.addTarget(obj)
|
||||
|
||||
def remove(self):
|
||||
self.roomData.removeTarget(self.owner)
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
class Trap(Component):
|
||||
|
||||
|
||||
def attach(self, obj):
|
||||
|
||||
if not obj.getComponent("fighter"):
|
||||
# todo: better exception
|
||||
raise Exception("Trap needs object with fighter component")
|
||||
|
||||
self.owner = obj
|
||||
self.fighter = obj.getComponent("fighter")
|
||||
|
||||
obj.addListener("objectenter", self.onEnter)
|
||||
|
||||
|
||||
def onEnter(self, owner, obj=None, *data):
|
||||
self.fighter.attack(obj)
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Volatile(Component):
|
||||
|
||||
|
||||
def __init__(self, duration):
|
||||
self.duration = duration
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.roomData = roomData
|
||||
roomData.setAlarm(stamp + self.duration, self.end)
|
||||
|
||||
def end(self):
|
||||
self.owner.remove()
|
||||
|
||||
|
||||
def toJSON(self):
|
||||
# better not to save volatile entities in the first place...
|
||||
return {
|
||||
"duration": self.duration
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
|
||||
|
||||
import random
|
||||
|
||||
from .component import Component
|
||||
|
||||
class Weather(Component):
|
||||
|
||||
def __init__(self, speed=1, spread=0, direction="south"):
|
||||
self.speed = speed
|
||||
self.spread = spread
|
||||
self.direction = direction
|
||||
|
||||
def attach(self, obj):
|
||||
self.owner = obj
|
||||
obj.addListener("roomjoin", self.roomJoin)
|
||||
|
||||
def roomJoin(self, o, roomData, stamp):
|
||||
self.moveEvent = roomData.getEvent("move")
|
||||
self.moveEvent.addListener(self.move)
|
||||
|
||||
def move(self):
|
||||
speed = self.speed
|
||||
for i in range(int(speed)):
|
||||
self.moveStep()
|
||||
if (speed - int(speed)) > random.random():
|
||||
self.moveStep()
|
||||
if self.spread > random.random():
|
||||
self.moveStep(random.choice(["east", "west"]))
|
||||
|
||||
def moveStep(self, direction=None):
|
||||
if direction is None:
|
||||
direction = self.direction
|
||||
if self.owner.getGround() is None:
|
||||
return
|
||||
neighbours = self.owner.getGround().getNeighbours()
|
||||
if direction in neighbours:
|
||||
newPlace = neighbours[direction]
|
||||
self.owner.place(newPlace)
|
||||
self.owner.trigger("move")
|
||||
else:
|
||||
self.owner.remove()
|
||||
|
||||
def remove(self):
|
||||
self.moveEvent.removeListener(self.move)
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
class Control:
|
||||
|
||||
name = None
|
||||
|
||||
def do_json(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, jsondata):
|
||||
name, arg = jsondata
|
||||
assert name == cls.name, "Control names do not match: {}, {}".format(name, cls.name)
|
||||
return cls(arg)
|
||||
|
||||
|
||||
_DIRECTIONS = {"north", "south", "east", "west"}
|
||||
_ALL_DIRECTIONS = _DIRECTIONS | {None}
|
||||
|
||||
|
||||
class MoveControl(Control):
|
||||
name = "move"
|
||||
def __init__(self, direction):
|
||||
assert direction in _DIRECTIONS, "Unknown direction: " + str(direction)
|
||||
self.direction = direction
|
||||
|
||||
def to_json(self):
|
||||
return [self.name, self.direction]
|
||||
|
||||
class RankedControl(Control):
|
||||
def __init__(self, rank=None):
|
||||
assert rank is None or isinstance(rank, int) and rank >= 0, "rank is not positive numeric: " + str(rank)
|
||||
self.rank = rank
|
||||
|
||||
def to_json(self):
|
||||
return [self.name, self.rank]
|
||||
|
||||
|
||||
class TakeControl(RankedControl):
|
||||
name = "take"
|
||||
|
||||
class DropControl(RankedControl):
|
||||
name = "drop"
|
||||
|
||||
class UseControl(RankedControl):
|
||||
name = "use"
|
||||
def __init__(self, container, rank=None):
|
||||
assert rank is None or isinstance(rank, int) and rank >= 0, "rank is not positive numeric: " + str(rank)
|
||||
assert container in ("inventory", "equipment"), "Invalid container: " + str(container)
|
||||
self.container = container
|
||||
self.rank = rank
|
||||
|
||||
def to_json(self):
|
||||
return [self.name, self.rank]
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, jsondata):
|
||||
name, container, rank = jsondata
|
||||
assert name == cls.name, "Control names do not match: {}, {}".format(name, cls.name)
|
||||
return cls(container, rank)
|
||||
|
||||
|
||||
class InteractControl(Control):
|
||||
name = "interact"
|
||||
def __init__(self, directions=(), parameter=None):
|
||||
for direction in directions:
|
||||
assert direction in _ALL_DIRECTIONS, "Unknown direction: " + str(direction)
|
||||
self.directions = directions
|
||||
self.parameter = parameter
|
||||
|
||||
def to_json(self):
|
||||
return [self.name, self.directions, parameter]
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, jsondata):
|
||||
if len(jsondata) == 3:
|
||||
name, directions, parameter = jsondata
|
||||
else:
|
||||
name, directions = jsondata
|
||||
parameter = None
|
||||
assert name == cls.name, "Control names do not match: {}, {}".format(name, cls.name)
|
||||
return cls(directions, parameter)
|
||||
|
||||
|
||||
class AttackControl(Control):
|
||||
name = "attack"
|
||||
def __init__(self, directions=()):
|
||||
for direction in directions:
|
||||
assert direction in _ALL_DIRECTIONS, "Unknown direction: " + str(direction)
|
||||
self.directions = directions
|
||||
|
||||
def to_json(self):
|
||||
return [self.name, self.directions]
|
||||
|
||||
|
||||
class SayControl(Control):
|
||||
name = "say"
|
||||
def __init__(self, text):
|
||||
assert isinstance(text, str), "text is not a string: " + str(text)
|
||||
self.text = text
|
||||
|
||||
def to_json(self):
|
||||
return [self.name, text]
|
||||
|
||||
_controls_by_name = {control.name: control for control in [
|
||||
MoveControl,
|
||||
TakeControl,
|
||||
DropControl,
|
||||
UseControl,
|
||||
InteractControl,
|
||||
AttackControl,
|
||||
SayControl
|
||||
]}
|
||||
|
||||
def controlFromJson(jsondata):
|
||||
return _controls_by_name[jsondata[0]].from_json(jsondata)
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
conversions = {
|
||||
"food": "eldritch_radish",
|
||||
"sword": "sword",
|
||||
"pebble": "pebble",
|
||||
"club": "club",
|
||||
"seed": "radishseed",
|
||||
"stone": "stone",
|
||||
"wall": "wall",
|
||||
"armour": "armour"
|
||||
}
|
||||
|
||||
def convert(item):
|
||||
name = item["name"]
|
||||
if name in conversions:
|
||||
return conversions[name]
|
||||
raise ValueError("Unknow old object:", str(item))
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
from .attackable import Attackable
|
||||
from .fighter import Fighter
|
||||
from .heal import Heal
|
||||
from .move import Move
|
||||
from .ai import AI
|
||||
from .input import Input
|
||||
from .faction import Faction
|
||||
from .events import Events
|
||||
from .loot import Loot
|
||||
from .portal import Portal
|
||||
from .dc import DC
|
||||
from .serialise import Serialise, Static
|
||||
from .inventory import Inventory
|
||||
from .listen import Listen
|
||||
from .periodic import Periodic
|
||||
from .equipment import Equipment
|
||||
from .spawner import Spawner, Squad
|
||||
from .item import Item
|
||||
from .equippable import Equippable
|
||||
from .buildable import Buildable
|
||||
from .exchanger import Exchange, Exchanger
|
||||
|
||||
from .messages import Message, EnterMessage, LootMessage, StartTimer, Create, SpawnMessage, UseMessage
|
||||
|
||||
class Remove:
|
||||
pass
|
||||
|
||||
class Dead:
|
||||
pass
|
||||
|
||||
|
||||
class Interact:
|
||||
|
||||
def __init__(self, *components):
|
||||
self.components = list(components)
|
||||
|
||||
class Trap:
|
||||
pass
|
||||
|
||||
class Home:
|
||||
def __init__(self, home):
|
||||
self.home = home
|
||||
|
||||
class Food:
|
||||
def __init__(self, healing):
|
||||
self.healing = healing
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
from .dc import DC
|
||||
|
||||
class AI(DC):
|
||||
|
||||
def __init__(self, viewDist=0, moveChance=1, home=None, homesickness=0.05):
|
||||
self.moveChance = moveChance
|
||||
self.viewDist = viewDist
|
||||
self.home = home # Should home be a place instead of object? that would reduce references. A: Yes
|
||||
self.homesickness = homesickness
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
class Attackable:
|
||||
|
||||
def __init__(self, maxHealth, health=None, defence=0, defense=None, onDie=None):
|
||||
self.maxHealth = maxHealth
|
||||
self.health = health or maxHealth
|
||||
if defense is not None:
|
||||
defence = defense
|
||||
self.defence = defence
|
||||
self.attacks = []
|
||||
self.onDie = onDie or []
|
||||
|
||||
def attack(self, strength, attacker):
|
||||
self.attacks.append(("attack", strength, attacker))
|
||||
|
||||
def heal(self, health, source):
|
||||
self.attacks.append(("heal", health, source))
|
||||
|
||||
def getHealth(self):
|
||||
return (self.health, self.maxHealth)
|
||||
|
||||
def healthFull(self):
|
||||
return self.health >= self.maxHealth
|
||||
|
||||
def isDead(self):
|
||||
return self.health <= 0
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
|
||||
class Buildable:
|
||||
def __init__(self, template, flagsneeded=frozenset(), blockingflags=frozenset()):
|
||||
self.template = template
|
||||
self.flagsneeded = flagsneeded
|
||||
self.blockingflags = blockingflags
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
class DC:
|
||||
allowMultiple = False
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
from .equippable import Equippable
|
||||
|
||||
class Equipment:
|
||||
|
||||
def __init__(self, slots):
|
||||
self.slots = slots
|
||||
|
||||
def getBonus(self, roomData, stat):
|
||||
bonus = 0
|
||||
for item in self.slots.values():
|
||||
if item is not None:
|
||||
bonus += roomData.getComponent(item, Equippable).stats.get(stat, 0)
|
||||
return bonus
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
class Equippable:
|
||||
|
||||
def __init__(self, slot, stats):
|
||||
self.slot = slot
|
||||
self.stats = stats
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
import collections
|
||||
|
||||
class Events:
|
||||
|
||||
def __init__(self):
|
||||
self.messages = collections.deque()
|
||||
|
||||
def add(self, message):
|
||||
self.messages.append(message)
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
|
||||
|
||||
class Exchange:
|
||||
|
||||
def __init__(self, products, costs, description=None, name=None):
|
||||
self.products = products
|
||||
self.costs = costs
|
||||
if description is None:
|
||||
description = "{} ({})".format(", ".join(str(p) for p in products), ", ".join(str(c) for c in costs))
|
||||
self.description = description
|
||||
self.name = name or id(self)
|
||||
|
||||
class Exchanger:
|
||||
|
||||
def __init__(self, options, description=""):
|
||||
self.options = {option.name: option for option in options}
|
||||
self.description = description
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
|
||||
class Faction:
|
||||
|
||||
def __init__(self, name):
|
||||
|
@ -29,6 +30,12 @@ NONE = Faction("none") # things that can always be attacked
|
|||
|
||||
GOOD.hates(NONE)
|
||||
EVIL.hates(NONE)
|
||||
NONE.hates(NONE)
|
||||
|
||||
Faction.NEUTRAL = NEUTRAL
|
||||
Faction.NONE = NONE
|
||||
Faction.GOOD = GOOD
|
||||
Faction.EVIL = EVIL
|
||||
|
||||
|
||||
factions = {}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
from ..pathfinding import distanceBetween
|
||||
from .dc import DC
|
||||
|
||||
class Fighter(DC):
|
||||
|
||||
def __init__(self, strength=0, slowness=1, range=1):
|
||||
self.strength = strength
|
||||
self.target = None
|
||||
self.slowness = slowness
|
||||
self.attackReady = 0
|
||||
self.range = range
|
||||
|
||||
|
||||
def inRange(self, owner, other):
|
||||
return distanceBetween(owner, other) <= self.range
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
class Solid:
|
||||
pass
|
||||
|
||||
class Floor:
|
||||
pass
|
||||
|
||||
class Buildable:
|
||||
pass
|
||||
|
||||
class Soil:
|
||||
pass
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
from .dc import DC
|
||||
|
||||
class Heal(DC):
|
||||
|
||||
def __init__(self, interval, amount=1):
|
||||
""" interval is the number of steps until next healing, amount is the amount of health that gets added in a healing """
|
||||
self.interval = interval
|
||||
self.amount = amount
|
||||
self.nextHeal = None
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
from .dc import DC
|
||||
|
||||
class Input(DC):
|
||||
|
||||
def __init__(self):
|
||||
self.action = None
|
||||
self.target = None
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
|
||||
class Inventory:
|
||||
|
||||
def __init__(self, capacity, items=None):
|
||||
self.items = items or []
|
||||
self.capacity = capacity
|
||||
|
||||
def add(self, item):
|
||||
self.items.insert(0, item)
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
class Item:
|
||||
|
||||
def __init__(self, onUse=None):
|
||||
self.onUse = onUse or []
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
|
||||
class Listen:
|
||||
|
||||
def __init__(self):
|
||||
self.sounds = []
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
from random import random as rand
|
||||
from .dc import DC
|
||||
from ..template import Template
|
||||
|
||||
class Loot(DC):
|
||||
|
||||
def __init__(self, items):
|
||||
self.items = []
|
||||
for item in items:
|
||||
if isinstance(item, Template) or isinstance(item, str):
|
||||
item = (item, 1)
|
||||
assert len(item) == 2, ValueError(item)
|
||||
template, chance = item
|
||||
if isinstance(template, str):
|
||||
template = Template(template)
|
||||
self.items.append((template, chance))
|
||||
|
||||
def pick(self):
|
||||
return [template for template, chance in self.items if chance > rand()]
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
from . import DC
|
||||
|
||||
class Message(DC):
|
||||
allowMultiple = True
|
||||
|
||||
class EnterMessage(Message):
|
||||
|
||||
def __init__(self, actor):
|
||||
self.actor = actor
|
||||
|
||||
|
||||
class LootMessage(Message):
|
||||
pass
|
||||
|
||||
class StartTimer(Message):
|
||||
pass
|
||||
|
||||
class Create(Message):
|
||||
|
||||
def __init__(self, *templates):
|
||||
self.templates = templates
|
||||
|
||||
class SpawnMessage(Message):
|
||||
pass
|
||||
|
||||
class EatMessage(Message):
|
||||
pass
|
||||
|
||||
class UseMessage(Message):
|
||||
def __init__(self, actor, parameter=None):
|
||||
self.actor = actor
|
||||
self.parameter = parameter
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
from .dc import DC
|
||||
|
||||
class Move(DC):
|
||||
|
||||
def __init__(self, slowness=1):
|
||||
self.direction = None
|
||||
self.slowness = slowness
|
||||
self.moveReady = 0
|
||||
|
||||
def canMove(self, obj, direction):
|
||||
neighbours = obj.getGround().getNeighbours()
|
||||
return direction in neighbours and neighbours[direction].accessible()
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
# todo: make sure growing continues in unloaded rooms
|
||||
|
||||
class Periodic:
|
||||
|
||||
def __init__(self, components, interval=None, targetTime=None, randomise=False):
|
||||
self.interval = interval
|
||||
self.components = components
|
||||
self.randomise = randomise
|
||||
self.targetTime = targetTime
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
from .dc import DC
|
||||
|
||||
class Portal(DC):
|
||||
|
||||
def __init__(self, destRoom, destPos=None, mask=(False, False)):
|
||||
self.destRoom = destRoom
|
||||
self.origin = destPos
|
||||
self.mask = mask
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
from .dc import DC
|
||||
from ..template import Template
|
||||
|
||||
|
||||
class Serialise(DC):
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def serialise(self, obj, roomData):
|
||||
return self.func(obj, roomData)
|
||||
|
||||
|
||||
def Static(name, *args, **kwargs):
|
||||
template = Template(name, *args, **kwargs)
|
||||
return Serialise(lambda obj, roomData: template)
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
import random
|
||||
import string
|
||||
|
||||
class Squad:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name is other.name
|
||||
|
||||
class Spawner:
|
||||
|
||||
def __init__(self, spawned, number=1, setHome=False, squad=None):
|
||||
self.spawned = spawned
|
||||
self.number = number
|
||||
if squad is None:
|
||||
squad = "_" + "".join(random.choices(string.ascii_lowercase, k=20))
|
||||
self.squad = squad
|
||||
self.setHome = setHome
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
from . import serialize
|
||||
from .eventtarget import EventTarget
|
||||
|
||||
import collections
|
||||
from .datacomponents import Events, Remove, Serialise
|
||||
|
||||
class Entity:
|
||||
""" Attempt to implement an entity component system
|
||||
|
@ -14,21 +14,29 @@ class Entity:
|
|||
Remove methods are for cleanup, like unsubscribing from events.
|
||||
"""
|
||||
|
||||
def __init__(self, sprite=' ', height=0, name=None, components=None, flags=None):
|
||||
if components is None:
|
||||
components = {}
|
||||
def __init__(self, sprite=' ', height=0, name=None, flags=None, dataComponents=None):
|
||||
if flags is None:
|
||||
flags = set()
|
||||
self.sprite = sprite # the name of the image to display for this entity
|
||||
self.height = height # if multiple objects are on a square, the tallest one is drawn
|
||||
self.name = name if name else sprite # human readable name/description
|
||||
self.components = components
|
||||
self.observable = EventTarget()
|
||||
self.flags = set(flags)
|
||||
self.ground = None
|
||||
self.roomData = None
|
||||
for component in self.components.values():
|
||||
component.attach(self)
|
||||
if dataComponents is None:
|
||||
dataComponents = []
|
||||
self.dataComponents = {}
|
||||
for component in dataComponents:
|
||||
if isinstance(component, type):
|
||||
compt = component
|
||||
component = component()
|
||||
else:
|
||||
compt = type(component)
|
||||
self.dataComponents[compt] = component
|
||||
|
||||
self.listeners = collections.defaultdict(dict)
|
||||
|
||||
|
||||
|
||||
|
||||
def construct(self, roomData, preserve=False, stamp=None):
|
||||
|
@ -36,16 +44,13 @@ class Entity:
|
|||
if preserve:
|
||||
roomData.preserveObject(self)
|
||||
self._preserve()
|
||||
self.roomData.addObj(self)
|
||||
if stamp is None:
|
||||
stamp = roomData.getStamp()
|
||||
self.trigger("roomjoin", roomData, stamp)
|
||||
|
||||
def hasComponent(self, name):
|
||||
return name in self.components
|
||||
|
||||
def getComponent(self, name):
|
||||
return self.components.get(name, None)
|
||||
|
||||
def getDataComponent(self, component):
|
||||
return self.roomData.getComponent(self, component)
|
||||
|
||||
def place(self, ground):
|
||||
if self.ground:
|
||||
|
@ -53,25 +58,38 @@ class Entity:
|
|||
self.ground = ground
|
||||
ground.addObj(self)
|
||||
|
||||
def remove(self):
|
||||
def unPlace(self):
|
||||
if self.ground:
|
||||
self.ground.removeObj(self)
|
||||
self.ground = None
|
||||
|
||||
|
||||
def remove(self):
|
||||
self.trigger("remove")
|
||||
self.roomData.addComponent(self, Remove)
|
||||
|
||||
|
||||
def doRemove(self):
|
||||
self.roomData.removeObj(self)
|
||||
if self.isPreserved():
|
||||
self.roomData.removePreserved(self)
|
||||
for component in self.components.values():
|
||||
component.remove()
|
||||
self.trigger("remove")
|
||||
self.unPlace()
|
||||
self.roomData = None
|
||||
|
||||
def addListener(self, event, callback, key=None):
|
||||
self.observable.addListener(event, callback, key)
|
||||
if key is None:
|
||||
key = callback
|
||||
self.listeners[event][key] = callback
|
||||
|
||||
def removeListener(self, event, key):
|
||||
self.observable.removeListener(event, key)
|
||||
self.listeners[event].pop(key)
|
||||
|
||||
def trigger(self, event, *args, **kwargs):
|
||||
self.observable.trigger(event, self, *args, **kwargs)
|
||||
messages = self.getDataComponent(Events)
|
||||
if messages is None:
|
||||
messages = Events()
|
||||
self.roomData.addComponent(self, messages)
|
||||
messages.add((event, list(args), dict(kwargs)))
|
||||
|
||||
def getSprite(self):
|
||||
return self.sprite
|
||||
|
@ -100,37 +118,11 @@ class Entity:
|
|||
def isPreserved(self):
|
||||
return "preserve" in self.flags
|
||||
|
||||
def toJSON(self):
|
||||
return {
|
||||
"sprite": self.sprite,
|
||||
"name": self.name,
|
||||
"height": self.height,
|
||||
"flags": list(self.flags),
|
||||
"components": {
|
||||
name: serialize.serialize(comp)
|
||||
for name, comp in self.components.items()
|
||||
}
|
||||
}
|
||||
|
||||
def serialize(self):
|
||||
if "serialize" not in self.components:
|
||||
return self.toJSON()
|
||||
return self.components["serialize"].serialize()
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, data):
|
||||
if data is None:
|
||||
if Serialise in self.dataComponents:
|
||||
return self.dataComponents[Serialise].serialise(self, self.roomData)
|
||||
else:
|
||||
return None
|
||||
return cls(
|
||||
sprite = data["sprite"],
|
||||
name = data["name"],
|
||||
height = data["height"],
|
||||
flags = data["flags"],
|
||||
components = {
|
||||
name: serialize.unserialize(comp)
|
||||
for name, comp in data["components"].items()
|
||||
}
|
||||
)
|
||||
|
||||
def getRoomData(self):
|
||||
return self.roomData
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
|
||||
|
||||
from . import gameobjects
|
||||
|
||||
class Exchange:
|
||||
|
||||
|
||||
def __init__(self, products, costs, description=None, name=None):
|
||||
self.products = products
|
||||
self.costs = costs
|
||||
if description is None:
|
||||
description = "{} ({})".format(", ".join(str(p) for p in products), ", ".join(str(c) for c in costs))
|
||||
self.description = description
|
||||
self.name = name or id(self)
|
||||
|
||||
|
||||
def perform(self, obj):
|
||||
inventory = obj.getComponent("inventory")
|
||||
if inventory is None:
|
||||
return
|
||||
costs = list(self.costs)
|
||||
toRemove = []
|
||||
for item in inventory.getItems():
|
||||
# get all the items to remove
|
||||
if item.name in costs:
|
||||
toRemove.append(item)
|
||||
costs.remove(item.name)
|
||||
if len(costs):
|
||||
# not all costs can be covered; other party can't afford trade
|
||||
return
|
||||
toAdd = [gameobjects.createEntity(product) for product in self.products]
|
||||
if not inventory.canAddAll(toAdd):
|
||||
return
|
||||
|
||||
for item in toRemove:
|
||||
inventory.drop(item)
|
||||
|
||||
for item in toAdd:
|
||||
inventory.add(item)
|
||||
|
||||
def act(self, obj):
|
||||
self.perform(obj)
|
||||
|
|
@ -15,24 +15,11 @@ from .structures import entities as structures
|
|||
entities = {**base, **crops, **items, **misc, **npcs, **structures, **exchangers}
|
||||
|
||||
|
||||
def makeEntity(entType, roomData, *args, preserve=False, **kwargs):
|
||||
entity = entities[entType](*args, **kwargs)
|
||||
entity.construct(roomData, preserve)
|
||||
return entity
|
||||
def createEntity(template):
|
||||
return entities[template.name](*template.args, **template.kwargs)
|
||||
|
||||
def createEntity(data):
|
||||
obj = None
|
||||
if isinstance(data, str):
|
||||
obj = entities[data]()
|
||||
elif isinstance(data, dict):
|
||||
if "type" in data:
|
||||
obj = entities[data["type"]](*(data.get("args", [])), **(data.get("kwargs", {})))
|
||||
else:
|
||||
obj = Entity.fromJSON(data)
|
||||
return obj
|
||||
|
||||
def buildEntity(data, roomData, preserve=False):
|
||||
obj = createEntity(data)
|
||||
def buildEntity(template, roomData, preserve=False):
|
||||
obj = createEntity(template)
|
||||
if obj is not None:
|
||||
obj.construct(roomData, preserve)
|
||||
return obj
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
from ..entity import Entity
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..datacomponents import Static
|
||||
import random
|
||||
|
||||
|
||||
|
@ -8,15 +8,15 @@ entities = {}
|
|||
|
||||
|
||||
entities["grass"] = lambda: Entity(
|
||||
sprite=random.choice(["ground"] + 2*["grass1", "grass2", "grass3"]), name="grass", height=0.1, flags={"floor", "soil"}, components={"serialize": Static("grass")})
|
||||
sprite=random.choice(["ground"] + 2*["grass1", "grass2", "grass3"]), name="grass", height=0.1, flags={"floor", "soil"}, dataComponents=[Static("grass")])
|
||||
|
||||
entities["greengrass"] = lambda: Entity(
|
||||
sprite=random.choice(["grass1", "grass2", "grass3"]), height=0.1, flags={"floor", "soil"}, components={"serialize": Static("greengrass")})
|
||||
sprite=random.choice(["grass1", "grass2", "grass3"]), height=0.1, flags={"floor", "soil"}, dataComponents=[Static("greengrass")])
|
||||
|
||||
entities["floor"] = lambda: Entity(sprite="floor", height=0.1, flags={"floor"}, components={"serialize": Static("floor")})
|
||||
entities["floor"] = lambda: Entity(sprite="floor", height=0.1, flags={"floor"}, dataComponents=[Static("floor")])
|
||||
|
||||
entities["ground"] = lambda: Entity(sprite="ground", height=0.1, flags={"floor", "soil"}, components={"serialize": Static("ground")})
|
||||
entities["ground"] = lambda: Entity(sprite="ground", height=0.1, flags={"floor", "soil"}, dataComponents=[Static("ground")])
|
||||
|
||||
entities["bridge"] = lambda small=False: Entity(sprite=("smallbridge" if small else "bridge"), height=0.1, flags={"floor"}, components={"serialize": Static("bridge", small)})
|
||||
entities["bridge"] = lambda small=False: Entity(sprite=("smallbridge" if small else "bridge"), height=0.1, flags={"floor"}, dataComponents=[Static("bridge", small)])
|
||||
|
||||
entities["water"] = lambda: Entity(sprite="water", height=0, components={"serialize": Static("water")})
|
||||
entities["water"] = lambda: Entity(sprite="water", height=0, dataComponents=[Static("water")])
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
|
||||
|
||||
from ..entity import Entity
|
||||
from ..components import Build, Food, Growing, Harvest, Loot
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..components import CustomSerializer as Custom
|
||||
from ..datacomponents import Interact, Loot, Remove, Serialise, Static, LootMessage, Periodic, StartTimer, Create, Item, Food, Buildable
|
||||
from ..template import Template
|
||||
|
||||
entities = {}
|
||||
|
||||
def cropSerializer(name):
|
||||
return lambda obj: {
|
||||
"type": name,
|
||||
"args": [],
|
||||
"kwargs": {
|
||||
"targetTime": obj.getComponent("grow").getTargetTime()}}
|
||||
return (lambda obj, roomData:
|
||||
Template(name, targetTime=roomData.getComponent(obj, Periodic).targetTime)
|
||||
)
|
||||
|
||||
class Stage:
|
||||
|
||||
|
@ -27,24 +24,29 @@ class Stage:
|
|||
def create(self, cropname="", nextstage=None, timestep=1):
|
||||
name = self.name.format(cropname)
|
||||
def makeEntity(targetTime=None):
|
||||
components = {}
|
||||
dataComponents = []
|
||||
if self.duration is not None:
|
||||
ongrow = [Remove, Create(Template(nextstage))]
|
||||
if targetTime is None:
|
||||
components["grow"] = Growing(nextstage, self.duration*timestep)
|
||||
timer = Periodic(ongrow, self.duration * timestep)
|
||||
else:
|
||||
components["grow"] = Growing(nextstage, targetTime=targetTime)
|
||||
components["serialize"] = Custom(cropSerializer(name))
|
||||
timer = Periodic(ongrow, targetTime=targetTime)
|
||||
dataComponents.append(timer)
|
||||
dataComponents.append(Serialise(cropSerializer(name)))
|
||||
dataComponents.append(StartTimer())
|
||||
else:
|
||||
components["serialize"] = Static(name)
|
||||
dataComponents.append(Static(name))
|
||||
if self.harvest is not None:
|
||||
components["interact"] = Harvest()
|
||||
components["loot"] = Loot(self.harvest)
|
||||
dataComponents.append(Interact(LootMessage, Remove))
|
||||
dataComponents.append(Loot(self.harvest))
|
||||
flags = {"occupied"}
|
||||
return Entity(
|
||||
sprite=self.sprite.format(cropname),
|
||||
height=self.height,
|
||||
name=self.shownname.format(cropname),
|
||||
flags={"occupied"},
|
||||
components=components)
|
||||
flags=flags,
|
||||
dataComponents=dataComponents
|
||||
)
|
||||
entities[name] = makeEntity
|
||||
|
||||
|
||||
|
@ -69,9 +71,8 @@ def createCrop(name, stages, timestep=1):
|
|||
sprite="seed",
|
||||
name=seedname,
|
||||
height=0.2,
|
||||
components={
|
||||
"item": Build(stagenames[0], flagsNeeded={"soil"}, blockingFlags={"occupied", "solid"}),
|
||||
"serialize": Static(seedname)})
|
||||
dataComponents=[Static(seedname), Item, Buildable(Template(stagenames[0]), flagsneeded={"soil"}, blockingflags={"occupied", "solid"})]
|
||||
)
|
||||
|
||||
for i, stage in enumerate(stages[:-1]):
|
||||
nextstage = stagenames[i+1]
|
||||
|
@ -83,19 +84,24 @@ createCrop("carrot", [
|
|||
Stage.Seed(20),
|
||||
Stage.Seedling(40),
|
||||
Stage("carrotplant", sprite="smallplant", height=0.5, harvest=[("carrot", 1), ("carrotseed", 1)])
|
||||
], 600)
|
||||
], 600)
|
||||
|
||||
entities["carrot"] = lambda: Entity(sprite="food", name="carrot", height=0.3, components={"item": Food(4), "serialize": Static("carrot")})
|
||||
entities["carrot"] = lambda: Entity(sprite="food", name="carrot", height=0.3, dataComponents=[Static("carrot"), Item, Food(5)])
|
||||
|
||||
|
||||
createCrop("radish", [
|
||||
Stage.Seed(3),
|
||||
Stage.Seedling(3),
|
||||
Stage.YoungPlant(6),
|
||||
Stage("{}plant", sprite="smallplant", height=0.5, harvest=[("radishseed", .92), ("radishseed", .20), ("radishes", .8), ("radishes", .4)])
|
||||
], 10)
|
||||
Stage(
|
||||
"{}plant",
|
||||
sprite="smallplant",
|
||||
height=0.5,
|
||||
harvest=[("radishseed", .92), ("radishseed", .20), ("radishes", .8), ("radishes", .4)]
|
||||
)
|
||||
], 10)
|
||||
|
||||
entities["radishes"] = lambda: Entity(sprite="food", name="radishes", height=0.3, components={"item": Food(2), "serialize": Static("radishes")})
|
||||
entities["radishes"] = lambda: Entity(sprite="food", name="radishes", height=0.3, dataComponents=[Static("radishes"), Item, Food(3)])
|
||||
|
||||
entities["food"] = entities["radishes"]
|
||||
entities["sownseed"] = entities["plantedradishseed"]
|
||||
|
@ -103,6 +109,7 @@ entities["youngplant"] = entities["youngradishplant"]
|
|||
entities["plant"] = entities["radishplant"]
|
||||
entities["seed"] = entities["radishseed"]
|
||||
|
||||
entities["eldritch_radish"] = lambda: Entity(sprite="food", name="eldritch radishes", height=0.3, dataComponents=[Static("eldritch_radish"), Item, Food(20)])
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,23 +2,24 @@
|
|||
|
||||
|
||||
from ..entity import Entity
|
||||
from .. import faction
|
||||
from ..option import Option
|
||||
from ..exchange import Exchange
|
||||
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..components import CustomSerializer as Custom
|
||||
from ..components import OptionMenu
|
||||
from ..components import Selectable
|
||||
from ..datacomponents import Static, Interact, Exchanger, Exchange
|
||||
|
||||
entities = {}
|
||||
|
||||
|
||||
entities["trader"] = lambda: Entity(sprite="sign", height=1, components={
|
||||
"interact": Selectable(),
|
||||
"options": OptionMenu("Trade", [
|
||||
Exchange(["carrotseed"], ["radishes"], None, "buy_carrotseeds"),
|
||||
Exchange(["pebble"], ["radishes"], None, "buy_pebble")]),
|
||||
"serialize": Static("trader")})
|
||||
entities["trader"] = lambda: Entity(
|
||||
sprite="sign",
|
||||
name="trader",
|
||||
height=1,
|
||||
dataComponents=[
|
||||
Static("trader"),
|
||||
Interact,
|
||||
Exchanger([
|
||||
Exchange(["carrotseed"], ["radishes"], None, "buy_carrotseeds"),
|
||||
Exchange(["pebble"], ["radishes"], None, "buy_pebble")
|
||||
], "Trade")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
|
||||
|
||||
from ..entity import Entity
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..components import Build, Item, Equippable
|
||||
from ..datacomponents import Static, Item, Equippable, Buildable
|
||||
from ..template import Template
|
||||
|
||||
entities = {}
|
||||
|
||||
|
||||
|
||||
entities["stone"] = lambda: Entity(sprite="stone", height=0.4, components={"item": Build("builtwall", flagsNeeded={"freeland"}, blockingFlags={"solid", "occupied"}), "serialize": Static("stone")})
|
||||
entities["stone"] = lambda: Entity(sprite="stone", height=0.4, dataComponents=[Static("stone"), Item, Build(Template("builtwall"), flagsneeded={"freeland"}, blockingflags={"solid", "occupied"})])
|
||||
|
||||
entities["pebble"] = lambda: Entity(sprite="pebble", height=0.2, components={"item": Item(), "serialize": Static("pebble")})
|
||||
entities["pebble"] = lambda: Entity(sprite="pebble", height=0.2, dataComponents=[Static("pebble"), Item])
|
||||
|
||||
|
||||
|
||||
|
||||
entities["sword"] = lambda: Entity(sprite="sword", height=0.5, components={"item": Equippable("hand", {"strength": 5}), "serialize": Static("sword")})
|
||||
entities["sword"] = lambda: Entity(sprite="sword", height=0.5, dataComponents=[Static("sword"), Item, Equippable("hand", {"strength": 5})])
|
||||
|
||||
entities["club"] = lambda: Entity(sprite="club", height=0.5, components={"item": Equippable("hand", {"strength": 3}), "serialize": Static("club")})
|
||||
entities["club"] = lambda: Entity(sprite="club", height=0.5, dataComponents=[Static("club"), Item, Equippable("hand", {"strength": 3})])
|
||||
|
||||
entities["weapon"] = lambda strength=0, name="weapon": Entity(sprite="sword", name=name, height=0.5, components={"item": Equippable("hand", {"strength": strength}), "serialize": Static("weapon", strength=strength)})
|
||||
entities["weapon"] = lambda strength=0, name="weapon": Entity(sprite="sword", name=name, height=0.5, dataComponents=[Static("weapon", strength=strength), Item. Equippable("hand", {"strength": strength})])
|
||||
|
||||
entities["armour"] = lambda: Entity(sprite="armour", height=0.5, components={"item": Equippable("body", {"defense": 100}), "serialize": Static("armour")})
|
||||
entities["armour"] = lambda: Entity(sprite="armour", height=0.5, dataComponents=[Static("armour"), Item, Equippable("body", {"defence": 100})])
|
||||
|
||||
|
||||
|
||||
entities["hardwood"] = lambda: Entity(sprite="hardwood", height=0.4, components={"item": Build("builtwall", flagsNeeded={"freeland"}, blockingFlags={"solid", "occupied"}), "serialize": Static("hardwood")})
|
||||
#entities["hardwood"] = lambda: Entity(sprite="hardwood", height=0.4, components={"item": Build("builtwall", flagsNeeded={"freeland"}, blockingFlags={"solid", "occupied"})}, dataComponents=[Static("hardwood"), Item])
|
||||
|
|
|
@ -2,31 +2,40 @@
|
|||
|
||||
|
||||
from ..entity import Entity
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..components import Portal, Spawner, Volatile, Weather
|
||||
from ..datacomponents import Portal, Static, Periodic, Remove, StartTimer, Spawner, SpawnMessage
|
||||
from ..template import Template
|
||||
|
||||
|
||||
entities = {}
|
||||
|
||||
|
||||
|
||||
entities["freeland"] = lambda: Entity(name="buildable", flags={"freeland"}, components={"serialize": Static("freeland")})
|
||||
entities["freeland"] = lambda: Entity(name="buildable", flags={"freeland"}, dataComponents=[Static("freeland")])
|
||||
|
||||
entities["spawner"] = lambda objType, number, delay, sprite=None, name=None, height=0, setHome=False, initialSpawn=True, objArgs=None, objKwargs=None: Entity(
|
||||
sprite=sprite, height=height, name=name, components={
|
||||
"spawn": Spawner(objType, number, delay, setHome, initialSpawn, objArgs, objKwargs),
|
||||
"serialize": Static("spawner", objType, number, delay, sprite, name, height, setHome, initialSpawn, objArgs, objKwargs)})
|
||||
entities["spawner"] = lambda template, number, delay, sprite=None, name=None, height=0, setHome=False, initialSpawn=True: Entity(
|
||||
sprite=sprite, height=height, name=name, dataComponents=[
|
||||
StartTimer,
|
||||
SpawnMessage if initialSpawn else None,
|
||||
Periodic([StartTimer, SpawnMessage], interval=delay, randomise=True),
|
||||
Spawner(Template.fromJSON(template), number, setHome=setHome),
|
||||
Static("spawner", template, number, delay, sprite, name, height, setHome, initialSpawn)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
entities["letter"] = lambda c: Entity(sprite="emptyletter-"+c, components={"serialize": Static("letter", c)})
|
||||
entities["letter"] = lambda c: Entity(sprite="emptyletter-"+c, dataComponents=[Static("letter", c)])
|
||||
|
||||
|
||||
entities["roomexit"] = lambda destRoom, destPos=None, mask=(False, False), sprite=" ", size=0: Entity(
|
||||
sprite=sprite, height=size, components={"collision": Portal(destRoom, destPos, mask), "serialize": Static(destRoom, destPos, mask, sprite, size)})
|
||||
sprite=sprite, height=size, dataComponents=[
|
||||
Portal(destRoom, destPos, mask),
|
||||
Static(destRoom, destPos, mask, sprite, size)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
entities["wound"] = lambda duration=4, height=0.2: Entity(sprite="wound", name="", height=height, components={"volatile": Volatile(duration), "serialize": Static(None)})
|
||||
entities["wound"] = lambda duration=4, height=0.2: Entity(sprite="wound", name="", height=height, dataComponents=[StartTimer(), Periodic([Remove], duration), Static(None)])
|
||||
|
||||
entities["raindrop"] = lambda: Entity(sprite="raindrop", name="", height=10, components={"weather": Weather(speed=2.5, spread=0.2), "serialize": Static(None)})
|
||||
#entities["raindrop"] = lambda: Entity(sprite="raindrop", name="", height=10, components={"weather": Weather(speed=2.5, spread=0.2)}, dataComponents=[Static(None)})
|
||||
|
||||
entities["snowflake"] = lambda: Entity(sprite="snowflake", name="", height=10, components={"weather": Weather(speed=1, spread=0.5), "serialize": Static(None)})
|
||||
#entities["snowflake"] = lambda: Entity(sprite="snowflake", name="", height=10, components={"weather": Weather(speed=1, spread=0.5), "serialize": Static(None)})
|
||||
|
|
|
@ -2,47 +2,58 @@
|
|||
|
||||
from ..entity import Entity
|
||||
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..components import Alignment, Fighter, Loot, MonsterAi, Move, RandomWalkController, Trap
|
||||
|
||||
from .. import faction
|
||||
from ..datacomponents import Fighter, Move, Attackable, AI, Faction, Loot, LootMessage, Trap, Static
|
||||
|
||||
entities = {}
|
||||
|
||||
|
||||
entities["rabbit"] = lambda: Entity(
|
||||
sprite="rabbit", name="bunny", height=1, components={"move": Move(slowness=4), "controller": RandomWalkController(moveChance=0.05), "serialize": Static("rabbit")})
|
||||
sprite="rabbit", name="bunny", height=1, dataComponents=[AI(moveChance=0.05), Move(slowness=4), Static("rabbit")]
|
||||
)
|
||||
|
||||
|
||||
entities["goblin"] = lambda home=None: Entity(sprite="goblin", height=1.2, components={
|
||||
"move": Move(slowness=3),
|
||||
"fighter": Fighter(maxHealth=15, strength=5, slowness=8),
|
||||
"alignment": Alignment(faction.EVIL),
|
||||
"controller": MonsterAi(viewDist=8, moveChance=0.02, home=home),
|
||||
"loot": Loot([("sword", .05), ("club", .1), ("radishes", .25)])
|
||||
})
|
||||
entities["goblin"] = lambda home=None: Entity(sprite="goblin", height=1.2, dataComponents=[
|
||||
Faction.EVIL,
|
||||
AI(viewDist=8, moveChance=0.02, home=home),
|
||||
Move(slowness=3),
|
||||
Attackable(maxHealth=15, onDie=[LootMessage]),
|
||||
Fighter(strength=5, slowness=8),
|
||||
Loot([("sword", .05), ("club", .1), ("radishes", .25)])
|
||||
]
|
||||
)
|
||||
|
||||
entities["troll"] = lambda home=None: Entity(sprite="troll", height=1.8, components={
|
||||
"move": Move(slowness=4),
|
||||
"fighter": Fighter(maxHealth=75, strength=15, slowness=10),
|
||||
"alignment": Alignment(faction.EVIL),
|
||||
"controller": MonsterAi(viewDist=8, moveChance=0.01, home=home),
|
||||
"loot": Loot([("stone", 1), ("stone", .3), ("pebble", .5), ("pebble", .5), ("pebble", .5)])
|
||||
})
|
||||
entities["troll"] = lambda home=None: Entity(sprite="troll", height=1.8, dataComponents=[
|
||||
Faction.EVIL,
|
||||
AI(viewDist=8, moveChance=0.01, home=home),
|
||||
Move(slowness=4),
|
||||
Attackable(maxHealth=75, onDie=[LootMessage]),
|
||||
Fighter(strength=15, slowness=10),
|
||||
Loot([("stone", 1), ("stone", .3), ("pebble", .5), ("pebble", .5), ("pebble", .5)])
|
||||
]
|
||||
)
|
||||
|
||||
entities["rat"] = lambda home=None: Entity(sprite="rat", height=1, components={
|
||||
"move": Move(slowness=3),
|
||||
"fighter": Fighter(maxHealth=8, strength=2, slowness=6),
|
||||
"alignment": Alignment(faction.EVIL),
|
||||
"controller": MonsterAi(viewDist=3, moveChance=0.08, home=home, homesickness=0.1),
|
||||
"loot": Loot([("radishseed", 0.9), ("radishseed", 0.3)])
|
||||
})
|
||||
entities["rat"] = lambda home=None: Entity(sprite="rat", height=1, dataComponents=[
|
||||
Faction.EVIL,
|
||||
AI(viewDist=3, moveChance=0.08, home=home, homesickness=0.1),
|
||||
Move(slowness=3),
|
||||
Attackable(maxHealth=8, onDie=[LootMessage]),
|
||||
Fighter(strength=2, slowness=6),
|
||||
Loot([("radishseed", 0.9), ("radishseed", 0.3)])
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
entities["dummy"] = lambda: Entity(
|
||||
sprite="dummy", height=1, flags={"occupied"}, components={"fighter": Fighter(maxHealth=20, strength=0), "alignment": Alignment(faction.NONE)})
|
||||
sprite="dummy", height=1, flags={"occupied"}, dataComponents=[
|
||||
Attackable(maxHealth=20),
|
||||
Faction.NONE
|
||||
])
|
||||
|
||||
entities["spiketrap"] = lambda damage=25: Entity(
|
||||
sprite="spikes", height=1, flags={"occupied"}, components={"fighter": Fighter(maxHealth=25, strength=damage, attackable=False), "collision": Trap(), "serialize": Static("spiketrap")})
|
||||
entities["spiketrap"] = lambda damage=15: Entity(
|
||||
sprite="spikes",
|
||||
height=1,
|
||||
flags={"occupied"},
|
||||
dataComponents=[Fighter(strength=damage, slowness=10), Trap(), Static("spiketrap")]
|
||||
)
|
||||
|
|
|
@ -2,58 +2,43 @@
|
|||
|
||||
|
||||
from ..entity import Entity
|
||||
from .. import faction
|
||||
|
||||
from ..components import StaticSerializer as Static
|
||||
from ..components import CustomSerializer as Custom
|
||||
from ..components import Change
|
||||
from ..components import Fighter
|
||||
from ..components import Alignment
|
||||
from ..components import Loot
|
||||
from ..datacomponents import Attackable, Faction, Loot, LootMessage, Interact, Remove, Serialise, Static, Create
|
||||
from ..template import Template
|
||||
|
||||
entities = {}
|
||||
|
||||
entities["wall"] = lambda: Entity(sprite="wall", height=2, flags={"solid"}, components={"serialize": Static("wall")})
|
||||
entities["wall"] = lambda: Entity(sprite="wall", height=2, flags={"solid"}, dataComponents=[Static("wall")])
|
||||
|
||||
|
||||
entities["rock"] = lambda: Entity(sprite="rock", height=10, flags={"solid"}, components={"serialize": Static("rock")})
|
||||
entities["rock"] = lambda: Entity(sprite="rock", height=10, flags={"solid"}, dataComponents=[Static("rock")])
|
||||
|
||||
entities["tree"] = lambda: Entity(sprite="tree", height=3, flags={"solid"}, components={"serialize": Static("tree")})
|
||||
entities["tree"] = lambda: Entity(sprite="tree", height=3, flags={"solid"}, dataComponents=[Static("tree")])
|
||||
|
||||
entities["house"] = lambda: Entity(sprite="house", height=3, flags={"solid"}, components={"serialize": Static("house")})
|
||||
entities["house"] = lambda: Entity(sprite="house", height=3, flags={"solid"}, dataComponents=[Static("house")])
|
||||
|
||||
entities["fence"] = lambda: Entity(sprite="fence", height=1, flags={"solid"}, components={"serialize": Static("fence")})
|
||||
entities["fence"] = lambda: Entity(sprite="fence", height=1, flags={"solid"}, dataComponents=[Static("fence")])
|
||||
|
||||
|
||||
entities["builtwall"] = lambda health=None: Entity(
|
||||
sprite="builtwall", height=2, flags={"solid"}, components={
|
||||
"fighter": Fighter(maxHealth=100, strength=0, health=None),
|
||||
"alignment": Alignment(faction.NONE),
|
||||
"loot": Loot([("stone", 1)]),
|
||||
"serialize": Custom(
|
||||
lambda obj: {
|
||||
"type": "builtwall",
|
||||
"kwargs": {"health": obj.getComponent("fighter").health}
|
||||
}
|
||||
)
|
||||
})
|
||||
sprite="builtwall",
|
||||
height=2,
|
||||
flags={"solid"},
|
||||
dataComponents=[
|
||||
Faction.NONE,
|
||||
Attackable(health=health, maxHealth=100, onDie=[LootMessage]),
|
||||
Serialise(
|
||||
lambda obj, roomData:
|
||||
Template("builtwall", health=roomData.getComponent(obj, Attackable).health)
|
||||
),
|
||||
Loot(["stone"])
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
entities["closeddoor"] = lambda: Entity(sprite="closeddoor", name="door", height=2, flags={"solid"}, components={"interact": Change("opendoor"), "serialize": Static("closeddoor")})
|
||||
entities["closeddoor"] = lambda: Entity(sprite="closeddoor", name="door", height=2, flags={"solid"}, dataComponents=[Interact(Remove, Create(Template("opendoor"))), Static("closeddoor")])
|
||||
|
||||
entities["opendoor"] = lambda: Entity(sprite="opendoor", name="door", height=1, flags={"occupied"}, components={"interact": Change("closeddoor"), "serialize": Static("opendoor")})
|
||||
|
||||
#entities["builtcloseddoor"] = lambda health=None: Entity(sprite="closeddoor", name="door", height=2, flags={"solid"}, components={
|
||||
#"interact": Change(transferComponents={"fighter"}),
|
||||
#"fighter": Fighter(maxHealth=100, health=health, strength=0),
|
||||
#"alignment": Alignment(faction.NONE),
|
||||
#"loot": Loot([("hardwood", 1)])})
|
||||
|
||||
#entities["builtopendoor"] = lambda health=None: Entity(sprite="opendoor", name="door", height=1, flags={"occupied"}, components={
|
||||
#"interact": Change(),
|
||||
#"fighter": Fighter(maxHealth=100, health=health, strength=0),
|
||||
#"alignment": Alignment(faction.NONE),
|
||||
#"loot": Loot([("hardwood", 1)])})
|
||||
entities["opendoor"] = lambda: Entity(sprite="opendoor", name="door", height=1, flags={"occupied"}, dataComponents=[Interact(Remove, Create(Template("closeddoor"))), Static("opendoor")])
|
||||
|
||||
|
||||
entities["engraved"] = lambda c: Entity(sprite="engravedwall-"+c, height=2, flags={"solid"}, components={"serialize": Static("wall", c)})
|
||||
entities["engraved"] = lambda c: Entity(sprite="engravedwall-"+c, height=2, flags={"solid"}, dataComponents=[Static("wall", c)])
|
||||
|
|
|
@ -10,6 +10,7 @@ import threading
|
|||
from . import view
|
||||
from . import socketserver as server
|
||||
from . import player
|
||||
from . import controls
|
||||
|
||||
from asciifarm.common import messages
|
||||
|
||||
|
@ -76,7 +77,7 @@ class GameServer:
|
|||
except json.JSONDecodeError:
|
||||
self.error(connection, "invalidmessage", "Invalid JSON")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(type(e), e, data)
|
||||
self.error(connection, "invalidmessage", "An unknown error occured in handling the message")
|
||||
|
||||
def handleMessage(self, connection, message):
|
||||
|
@ -107,7 +108,8 @@ class GameServer:
|
|||
|
||||
def handleInputMessage(self, connection, message):
|
||||
if connection in self.connections:
|
||||
self.messages.put(("input", self.connections[connection], message.inp))
|
||||
control = controls.controlFromJson(message.inp)
|
||||
self.messages.put(("input", self.connections[connection], control))
|
||||
|
||||
def handleChatMessage(self, connection, msg):
|
||||
name = self.connections[connection]
|
||||
|
|
|
@ -77,6 +77,6 @@ class GroundPatch:
|
|||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state['neighbours'] = None
|
||||
state["neighbours"] = None
|
||||
return state
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ thisPath = os.path.dirname(__file__)
|
|||
farmsPath = os.path.join(thisPath, "..")
|
||||
|
||||
defaultWorld = os.path.join(farmsPath, "maps", "world.json")
|
||||
defaultSaveDir = os.path.join(farmsPath, "saves")
|
||||
defaultSaveDir = os.path.join(farmsPath, "..", "saves")
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
|
@ -37,7 +37,7 @@ def main(argv=None):
|
|||
|
||||
sockets = []
|
||||
for socktype, address in socketargs:
|
||||
assert socktype in ["abstract", "unix", "inet"]
|
||||
assert socktype in ["abstract", "unix", "inet"], "Unknown socket type: " + str(socktype)
|
||||
if address is "":
|
||||
address = defaultAdresses[socktype]
|
||||
if socktype == "abstract":
|
||||
|
|
|
@ -1,21 +1,9 @@
|
|||
|
||||
# this package is not used, but it is important that it is imported before any component modules
|
||||
# importing components before gameobjects is loaded will result in a cyclic dependency problem
|
||||
from . import gameobjects
|
||||
|
||||
|
||||
from .components.inventory import Inventory
|
||||
from .components.inputcontroller import InputController
|
||||
from .components.move import Move
|
||||
from .components.fighter import Fighter
|
||||
from .components.healing import Healing
|
||||
from .components.alignment import Alignment
|
||||
from .components.target import Target
|
||||
from .components.equipment import Equipment
|
||||
from .components.listen import Listen
|
||||
from .components import Select
|
||||
from . import faction
|
||||
from .datacomponents import Inventory, Attackable, Move, Fighter, Heal, Input, Faction, Listen, Equipment
|
||||
from .template import Template
|
||||
from . import entity
|
||||
from .controls import Control
|
||||
|
||||
class Player:
|
||||
|
||||
|
@ -25,7 +13,7 @@ class Player:
|
|||
self.world = world
|
||||
self.roomname = None
|
||||
self.entity = None
|
||||
self.inventory = Inventory(10)
|
||||
self.inventory = Inventory(10) # Todo: make player inventory more abstract; just copy the contents to actual inventory on construction
|
||||
self.equipment = Equipment({"hand": None, "body": None})
|
||||
self.health = None
|
||||
self.maxHealth = 50
|
||||
|
@ -59,19 +47,25 @@ class Player:
|
|||
sprite = "player",
|
||||
height = 2,
|
||||
name = '&' + self.name,
|
||||
components={
|
||||
"inventory": self.inventory,
|
||||
"move": Move(slowness=2),
|
||||
"controller": InputController(),
|
||||
"fighter": Fighter(self.maxHealth, 5, slowness=8, health=self.health or self.maxHealth),
|
||||
"alignment": Alignment(faction.GOOD),
|
||||
"heal": Healing(interval=50),
|
||||
"target": Target(),
|
||||
"equipment": self.equipment,
|
||||
"listen": Listen(),
|
||||
"select": Select()
|
||||
})
|
||||
self.entity.construct(room.getRoomData())
|
||||
dataComponents = [
|
||||
self.inventory,
|
||||
self.equipment,
|
||||
Faction.GOOD,
|
||||
Input(),
|
||||
Listen(),
|
||||
Move(slowness=2),
|
||||
Heal(interval=50),
|
||||
Fighter(strength=5, slowness=8),
|
||||
Attackable(maxHealth=self.maxHealth, health=self.health or self.maxHealth)
|
||||
]
|
||||
)
|
||||
roomData = room.getRoomData()
|
||||
self.entity.construct(roomData)
|
||||
for item in self.inventory.items:
|
||||
item.construct(roomData)
|
||||
for item in self.equipment.slots.values():
|
||||
if item is not None:
|
||||
item.construct(roomData)
|
||||
for attr in dir(self):
|
||||
if attr.startswith("on_"):
|
||||
self.entity.addListener(attr[3:], self.__getattribute__(attr))
|
||||
|
@ -131,21 +125,14 @@ class Player:
|
|||
def on_selection(self, o, obj):
|
||||
self.changes.add("selection")
|
||||
|
||||
def on_sound(self, o, source, text):
|
||||
if source is not None:
|
||||
text = source.getName() + ": " + text
|
||||
self.messages.append([text, "world"])
|
||||
|
||||
|
||||
def control(self, action):
|
||||
if not self.entity or not (isinstance(action, list) or isinstance(action, tuple)) or len(action) < 1:
|
||||
if not self.entity or not isinstance(action, Control):
|
||||
return
|
||||
controller = self.entity.getComponent("controller")
|
||||
controller.setAction(action)
|
||||
self.entity.getDataComponent(Input).action = action
|
||||
|
||||
def getHealthPair(self):
|
||||
if self.entity:
|
||||
return self.entity.getComponent("fighter").getHealth()
|
||||
return self.entity.getDataComponent(Attackable).getHealth()
|
||||
else:
|
||||
return (0, None)
|
||||
|
||||
|
@ -159,21 +146,22 @@ class Player:
|
|||
|
||||
def getInventory(self):
|
||||
if self.entity:
|
||||
return self.inventory.getItems()
|
||||
return self.inventory.items
|
||||
else:
|
||||
return []
|
||||
|
||||
def getEquipment(self):
|
||||
if self.entity:
|
||||
return self.equipment.getSlots()
|
||||
return self.equipment.slots
|
||||
else:
|
||||
return {}
|
||||
|
||||
def getInteractions(self):
|
||||
if not self.entity:
|
||||
return []
|
||||
controller = self.entity.getComponent("controller")
|
||||
return controller.getInteractions()
|
||||
return NotImplemented
|
||||
#if not self.entity:
|
||||
#return []
|
||||
#controller = self.entity.getComponent("controller")
|
||||
#return controller.getInteractions()
|
||||
|
||||
def getGroundObjs(self):
|
||||
if not self.entity:
|
||||
|
@ -204,9 +192,14 @@ class Player:
|
|||
print(msg)
|
||||
|
||||
def readMessages(self):
|
||||
m = self.messages
|
||||
self.messages = []
|
||||
return m
|
||||
messages = []
|
||||
listen = self.entity.getDataComponent(Listen)
|
||||
for source, text in listen.sounds:
|
||||
if source is not None:
|
||||
text = source.getName() + ": " + text
|
||||
messages.append([text, "world"])
|
||||
listen.sounds = []
|
||||
return messages
|
||||
|
||||
def readChanges(self):
|
||||
changes = self.changes
|
||||
|
@ -220,8 +213,9 @@ class Player:
|
|||
return {
|
||||
"name": self.name,
|
||||
"roomname": self.roomname,
|
||||
"inventory": self.inventory.toJSON(),
|
||||
"equipment": self.equipment.toJSON(),
|
||||
"inventory": {"capacity": self.inventory.capacity, "items": [item.serialize().toJSON() for item in self.inventory.items]},
|
||||
"equipment": {slot: (item.serialize().toJSON() if item is not None else None) for slot, item in self.equipment.slots.items()},
|
||||
#self.equipment.toJSON(),
|
||||
"health": self.getHealth(),
|
||||
"maxhealth": self.maxHealth
|
||||
}
|
||||
|
@ -231,8 +225,11 @@ class Player:
|
|||
self = cls(data["name"], world)
|
||||
self.health = data["health"]
|
||||
self.maxHealth = data["maxhealth"]
|
||||
self.inventory = Inventory.fromJSON(data["inventory"])
|
||||
self.equipment = Equipment.fromJSON(data["equipment"])
|
||||
self.inventory = Inventory(data["inventory"]["capacity"], [gameobjects.createEntity(Template.fromJSON(item)) for item in data["inventory"]["items"]])
|
||||
for slot, item in data["equipment"].items():
|
||||
if item is not None:
|
||||
self.equipment.slots[slot] = gameobjects.createEntity(Template.fromJSON(item))
|
||||
#Equipment.fromJSON(data["equipment"])
|
||||
self.roomname = data["roomname"]
|
||||
|
||||
return self
|
||||
|
|
|
@ -7,8 +7,11 @@ from . import event
|
|||
from . import entity
|
||||
from . import roomdata
|
||||
from . import serialize
|
||||
from .template import Template
|
||||
|
||||
|
||||
from .systems import fight, attacked, heal, move, controlai, control, handleevents, remove, droploot, clearinbox, trap, teleport, sound, checktimers, create, spawn, eat, equip, build, exchange
|
||||
|
||||
class Room:
|
||||
|
||||
|
||||
|
@ -23,16 +26,7 @@ class Room:
|
|||
|
||||
self.lastStepStamp = 0
|
||||
|
||||
self.roomData = roomdata.RoomData(events={
|
||||
"control": event.Event(),
|
||||
"move": event.Event(),
|
||||
"fight": event.Event(),
|
||||
"update": event.Event(),
|
||||
"sound": event.Event()
|
||||
})
|
||||
def logSound(source, text):
|
||||
print("{}: {}: {}".format(self.name, source.getName(), text))
|
||||
self.roomData.getEvent("sound").addListener(logSound)
|
||||
self.roomData = roomdata.RoomData()
|
||||
|
||||
|
||||
self.places = data.get("places", {})
|
||||
|
@ -48,7 +42,7 @@ class Room:
|
|||
if not isinstance(val, list) :
|
||||
val = [val]
|
||||
for obj in val:
|
||||
ent = gameobjects.buildEntity(obj, self.roomData)
|
||||
ent = gameobjects.buildEntity(Template.fromJSON(obj), self.roomData)
|
||||
self.addObj((x, y), ent)
|
||||
|
||||
if preserved is not None:
|
||||
|
@ -56,6 +50,7 @@ class Room:
|
|||
|
||||
self.resetChangedCells()
|
||||
|
||||
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
@ -80,10 +75,32 @@ class Room:
|
|||
self.roomData.setStamp(stepStamp)
|
||||
|
||||
self.roomData.triggerAlarms()
|
||||
self.roomData.getEvent("control").trigger()
|
||||
self.roomData.getEvent("move").trigger()
|
||||
self.roomData.getEvent("fight").trigger()
|
||||
self.roomData.getEvent("update").trigger(timePassed, stepStamp)
|
||||
|
||||
systems = [
|
||||
control,
|
||||
controlai,
|
||||
move,
|
||||
trap,
|
||||
teleport,
|
||||
fight,
|
||||
heal,
|
||||
eat,
|
||||
equip,
|
||||
build,
|
||||
exchange,
|
||||
attacked,
|
||||
droploot,
|
||||
spawn,
|
||||
create,
|
||||
handleevents,
|
||||
checktimers,
|
||||
sound,
|
||||
clearinbox,
|
||||
remove
|
||||
]
|
||||
for system in systems:
|
||||
system(self.roomData)
|
||||
|
||||
self.lastStepStamp = stepStamp
|
||||
|
||||
def getSprites(self, pos):
|
||||
|
@ -144,17 +161,13 @@ class Room:
|
|||
def getPreserved(self):
|
||||
return {
|
||||
"changes": [
|
||||
(obj.getGround().getPos(), obj.serialize())
|
||||
for obj in self.roomData.getPreserved()],
|
||||
(obj.getGround().getPos(), obj.serialize().toJSON())
|
||||
for obj in self.roomData.getPreserved() if obj.getGround()],
|
||||
"step": self.lastStepStamp}
|
||||
|
||||
def loadPreserved(self, objects):
|
||||
if isinstance(objects, list):
|
||||
# just for rooms that have been saved in the old format
|
||||
# when there are no such rooms anymore, this can be removed
|
||||
objects = {"changes": objects}
|
||||
for (pos, objData) in objects["changes"]:
|
||||
obj = gameobjects.buildEntity(objData, self.roomData, preserve=True)
|
||||
obj = gameobjects.buildEntity(Template.fromJSON(objData), self.roomData, preserve=True)
|
||||
self.addObj(tuple(pos), obj)
|
||||
self.lastStepStamp = objects.get("step")
|
||||
self.roomData.setStamp(self.lastStepStamp)
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
|
||||
import heapq
|
||||
import itertools
|
||||
import collections
|
||||
counter = itertools.count() # unique sequence count
|
||||
|
||||
from .systems.fight import fight
|
||||
from .systems.attacked import attacked
|
||||
from .systems.heal import heal
|
||||
from .systems.move import move
|
||||
from .systems.controlai import controlai
|
||||
from .systems.controlinput import control
|
||||
|
||||
from .datacomponents import DC
|
||||
|
||||
|
||||
class RoomData:
|
||||
|
||||
|
@ -11,11 +21,7 @@ class RoomData:
|
|||
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=None):
|
||||
if events is None:
|
||||
events = []
|
||||
|
||||
self.events = events
|
||||
def __init__(self):
|
||||
self.targets = set()
|
||||
|
||||
self.preservedObjects = set()
|
||||
|
@ -23,20 +29,83 @@ class RoomData:
|
|||
self.stepStamp = 0
|
||||
|
||||
self.alarms = [] # treat as priority queue
|
||||
self.postponed = []
|
||||
|
||||
self.objects = set()
|
||||
|
||||
self.sounds = []
|
||||
|
||||
self.dataComponents = collections.defaultdict(set) # type: {str: set(Entity)}
|
||||
|
||||
def addObj(self, obj):
|
||||
|
||||
self.objects.add(obj)
|
||||
for compt in obj.dataComponents:
|
||||
self.dataComponents[compt].add(obj)
|
||||
|
||||
def removeObj(self, obj):
|
||||
self.objects.remove(obj)
|
||||
for compt in obj.dataComponents:
|
||||
if obj not in self.dataComponents[compt]:
|
||||
print(compt, obj.toJSON())
|
||||
self.dataComponents[compt].remove(obj)
|
||||
|
||||
|
||||
def getEvent(self, name):
|
||||
return self.events[name]
|
||||
def addComponent(self, obj, component):
|
||||
if isinstance(component, type):
|
||||
compt = component
|
||||
component = component()
|
||||
else:
|
||||
compt = type(component)
|
||||
self.dataComponents[compt].add(obj)
|
||||
if isinstance(component, DC) and component.allowMultiple:
|
||||
if compt not in obj.dataComponents:
|
||||
obj.dataComponents[compt] = []
|
||||
obj.dataComponents[compt].append(component)
|
||||
else:
|
||||
obj.dataComponents[compt] = component
|
||||
|
||||
def removeComponent(self, obj, component, gone_ok=False):
|
||||
if isinstance(component, type):
|
||||
compt = component
|
||||
component = self.getComponent(obj, compt)
|
||||
else:
|
||||
compt = type(component)
|
||||
if component is None:
|
||||
if gone_ok:
|
||||
return
|
||||
else:
|
||||
raise KeyError("object {} has no component {}".format(str(object), str(component)))
|
||||
if isinstance(component, DC) and component.allowMultiple:
|
||||
l = obj.dataComponents[compt]
|
||||
l.remove(component)
|
||||
if not l:
|
||||
self.dataComponents[compt].remove(l)
|
||||
else:
|
||||
self.dataComponents[compt].remove(obj)
|
||||
obj.dataComponents.pop(compt)
|
||||
|
||||
def addTarget(self, obj):
|
||||
self.targets.add(obj)
|
||||
def clearComponent(self, compt):
|
||||
for entity in self.dataComponents[compt]:
|
||||
del entity.dataComponents[compt]
|
||||
self.dataComponents[compt] = set()
|
||||
|
||||
def removeTarget(self, obj):
|
||||
self.targets.discard(obj)
|
||||
def getComponent(self, obj, component):
|
||||
return obj.dataComponents.get(component)
|
||||
|
||||
def getTargets(self):
|
||||
return frozenset(self.targets)
|
||||
def getEntities(self, compts, combinator="intersect", avoid=None):
|
||||
|
||||
entities = set(self.dataComponents[compts[0]])
|
||||
if combinator == "intersect":
|
||||
for compt in compts[1:]:
|
||||
entities &= self.dataComponents[compt]
|
||||
elif combinator == "union":
|
||||
for compt in compts[1:]:
|
||||
entities |= self.dataComponents[compt]
|
||||
if avoid is not None:
|
||||
for compt in avoid:
|
||||
entities -= self.dataComponents[compt]
|
||||
return entities
|
||||
|
||||
def preserveObject(self, obj):
|
||||
self.preservedObjects.add(obj)
|
||||
|
@ -54,6 +123,12 @@ class RoomData:
|
|||
heapq.heappush(self.alarms, (stamp, count, callback))
|
||||
|
||||
def triggerAlarms(self):
|
||||
while self.postponed and self.postponed[0][0] <= self.stepStamp:
|
||||
_plannedTime, _count, obj, components = heapq.heappop(self.postponed)
|
||||
if obj not in self.objects:
|
||||
return
|
||||
for component in components:
|
||||
self.addComponent(obj, component)
|
||||
while self.alarms and self.alarms[0][0] <= self.stepStamp:
|
||||
_plannedTime, _count, callback = heapq.heappop(self.alarms)
|
||||
callback()
|
||||
|
@ -64,3 +139,11 @@ class RoomData:
|
|||
def getStamp(self):
|
||||
return self.stepStamp
|
||||
|
||||
def makeSound(self, source, text):
|
||||
self.sounds.append((source, text))
|
||||
|
||||
def postpone(self, stamp, obj, *components):
|
||||
count = next(counter) # tiebreaker for when stamps are equal
|
||||
# see: https://docs.python.org/3/library/heapq.html#priority-queue-implementation-notes
|
||||
heapq.heappush(self.postponed, (stamp, count, obj, list(components)))
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
|
||||
def system(components, avoid=None):
|
||||
assert isinstance(components, list) or isinstance(components, tuple)
|
||||
def system_wrapper(func):
|
||||
def system_impl(roomData):
|
||||
entities = roomData.getEntities(components, avoid=avoid)
|
||||
for entity in entities:
|
||||
comps = [roomData.getComponent(entity, comp) for comp in components]
|
||||
func(entity, roomData, *comps)
|
||||
return system_impl
|
||||
return system_wrapper
|
|
@ -0,0 +1,20 @@
|
|||
from .attacked import attacked
|
||||
from .controlai import controlai
|
||||
from .controlinput import control
|
||||
from .fight import fight
|
||||
from .handleevents import handleevents
|
||||
from .heal import heal
|
||||
from .move import move
|
||||
from .remove import remove
|
||||
from .droploot import droploot
|
||||
from .clearinbox import clearinbox
|
||||
from .trap import trap
|
||||
from .teleport import teleport
|
||||
from .sound import sound
|
||||
from .checktimers import checktimers
|
||||
from .create import create
|
||||
from .spawn import spawn
|
||||
from .eat import eat
|
||||
from .equip import equip
|
||||
from .build import build
|
||||
from .exchange import exchange
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
|
||||
import random
|
||||
|
||||
from asciifarm.common import utils
|
||||
from .. import gameobjects
|
||||
from ..system import system
|
||||
from ..datacomponents import Attackable, Input, StartTimer, Periodic, Equipment
|
||||
from ..template import Template
|
||||
|
||||
@system([Attackable])
|
||||
def attacked(obj, roomData, attackable):
|
||||
for type, strength, attacker in attackable.attacks:
|
||||
if type == "attack":
|
||||
defence = attackable.defence
|
||||
equipment = roomData.getComponent(obj, Equipment)
|
||||
if equipment is not None:
|
||||
defence += equipment.getBonus(roomData, "defence")
|
||||
damage = random.randint(0, int(100*strength / (defence + 100)))
|
||||
elif type == "heal":
|
||||
damage = -strength
|
||||
else:
|
||||
raise ValueError("Unknown attack type " + type)
|
||||
attackable.health -= damage
|
||||
attackable.health = utils.clamp(attackable.health, 0, attackable.maxHealth)
|
||||
|
||||
# should this be it's own component? ('bleeding' for example)
|
||||
if damage > 0 and obj.getGround() is not None:
|
||||
wound = gameobjects.buildEntity(Template("wound", 4, obj.getHeight() - 0.01), roomData)
|
||||
wound.place(obj.getGround())
|
||||
|
||||
if type == "attack":
|
||||
obj.trigger("damage", attacker, damage)
|
||||
attacker.trigger("attack", obj, damage)
|
||||
input = roomData.getComponent(obj, Input)
|
||||
if input is not None:
|
||||
input.target = attacker # retaliation
|
||||
elif type == "heal":
|
||||
obj.trigger("heal", attacker, -damage)
|
||||
|
||||
if attackable.isDead():
|
||||
obj.trigger("die", attacker)
|
||||
attacker.trigger("kill", obj)
|
||||
for component in attackable.onDie:
|
||||
roomData.addComponent(obj, component)
|
||||
obj.remove()
|
||||
attackable.attacks = []
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
from ..system import system
|
||||
from ..datacomponents import UseMessage, Buildable, Remove, Inventory
|
||||
from .. import gameobjects
|
||||
|
||||
@system([UseMessage, Buildable])
|
||||
def build(obj, roomData, use, buildable):
|
||||
actor = use[0].actor
|
||||
ground = actor.getGround()
|
||||
print("building")
|
||||
groundFlags = ground.getFlags()
|
||||
if not buildable.flagsneeded <= groundFlags or groundFlags & buildable.blockingflags: # <= means subset when applied on sets
|
||||
# groundFlags must contain all of self.flagsNeeded, and none of self.blockingFlags
|
||||
return
|
||||
builtobj = gameobjects.buildEntity(buildable.template, roomData, preserve=True)
|
||||
builtobj.place(ground)
|
||||
roomData.addComponent(obj, Remove)
|
||||
inv = roomData.getComponent(actor, Inventory)
|
||||
if inv is not None:
|
||||
inv.items.remove(obj)
|
||||
actor.trigger("inventorychange")
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
import random
|
||||
|
||||
from ..system import system
|
||||
from ..datacomponents import StartTimer, Periodic
|
||||
|
||||
@system([StartTimer, Periodic])
|
||||
def checktimers(obj, roomData, _start, periodic):
|
||||
if periodic.targetTime is None:
|
||||
interval = periodic.interval
|
||||
if interval is None:
|
||||
raise ValueError("Neither interval nor targettime set for", str(obj), str(periodic))
|
||||
if periodic.randomise:
|
||||
interval = random.triangular(interval/2, interval*2, interval)
|
||||
periodic.targetTime = roomData.getStamp() + interval
|
||||
roomData.postpone(periodic.targetTime, obj, *periodic.components)
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
from ..datacomponents import Message
|
||||
|
||||
|
||||
def clearinbox(roomData):
|
||||
for compt, entities in roomData.dataComponents.items():
|
||||
if issubclass(compt, Message):
|
||||
roomData.clearComponent(compt)
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
import random
|
||||
|
||||
from .. import pathfinding
|
||||
from ..datacomponents import AI, Move, Fighter, Faction, Attackable, Home
|
||||
from ..system import system
|
||||
|
||||
@system([AI, Move])
|
||||
def controlai(obj, roomData, ai, movable):
|
||||
|
||||
fighter = roomData.getComponent(obj, Fighter)
|
||||
if fighter is not None:
|
||||
closestDistance = ai.viewDist + 1
|
||||
closest = None
|
||||
alignment = roomData.getComponent(obj, Faction) or Faction.NONE
|
||||
for target in roomData.dataComponents[Attackable]:
|
||||
distance = pathfinding.distanceBetween(obj, target)
|
||||
if (alignment.isEnemy(roomData.getComponent(target, Faction) or Faction.NONE)) and distance < closestDistance:
|
||||
closestDistance = distance
|
||||
closest = target
|
||||
if closest:
|
||||
if fighter.inRange(obj, closest):
|
||||
fighter.target = closest
|
||||
else:
|
||||
movable.direction = pathfinding.stepTo(obj, closest)
|
||||
return
|
||||
|
||||
if random.random() < ai.moveChance:
|
||||
home = roomData.getComponent(obj, Home)
|
||||
if home is not None and home.home.inRoom() and random.random() < (ai.homesickness * pathfinding.distanceBetween(obj, home.home)):
|
||||
direction = pathfinding.stepTo(obj, home.home)
|
||||
else:
|
||||
direction = random.choice(["north", "south", "east", "west"])
|
||||
movable.direction = direction
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
from ..datacomponents import Input, Fighter, Move, Faction, Interact, Inventory, Equipment, Attackable, Item, UseMessage
|
||||
from ..system import system
|
||||
#from ..controls import MoveAction, TakeAction, DropAction, UseAction
|
||||
|
||||
@system([Input, Fighter, Move])
|
||||
def control(obj, roomData, input, fighter, *_args):
|
||||
action = input.action
|
||||
if action:
|
||||
input.target = None
|
||||
input.action = None
|
||||
if action is not None:
|
||||
executeAction(obj, roomData, action)
|
||||
if input.target:
|
||||
fighter.target = input.target
|
||||
|
||||
def executeAction(obj, roomData, action):
|
||||
kind = action.name
|
||||
try:
|
||||
handler = handlers.get(kind)
|
||||
except TypeError:
|
||||
handler = None
|
||||
if handler is None:
|
||||
print("invalid action", action)
|
||||
return
|
||||
handler(obj, roomData, action)
|
||||
|
||||
def do_move(obj, roomData, action):
|
||||
direction = action.direction
|
||||
roomData.getComponent(obj, Move).direction = direction
|
||||
|
||||
def do_take(obj, roomData, action):
|
||||
rank = action.rank
|
||||
inventory = roomData.getComponent(obj, Inventory)
|
||||
if inventory is None or len(inventory.items) >= inventory.capacity:
|
||||
# can't take anything if there is no inventory or if it's full
|
||||
return
|
||||
objects = obj.getNearObjects()
|
||||
if rank is not None:
|
||||
if rank not in range(len(objects)):
|
||||
return
|
||||
objects = [objects[rank]]
|
||||
for item in objects:
|
||||
if roomData.getComponent(item, Item) is not None:
|
||||
inventory.add(item)
|
||||
obj.trigger("inventorychange")
|
||||
item.unPlace()
|
||||
break
|
||||
|
||||
def do_drop(obj, roomData, action):
|
||||
rank = action.rank
|
||||
inventory = roomData.getComponent(obj, Inventory)
|
||||
if inventory is None:
|
||||
return False
|
||||
if rank is None:
|
||||
rank = 0
|
||||
if rank not in range(len(inventory.items)):
|
||||
return False
|
||||
item = inventory.items[rank]
|
||||
inventory.items.remove(item)
|
||||
obj.trigger("inventorychange")
|
||||
item.place(obj.getGround())
|
||||
return True
|
||||
|
||||
def do_use(obj, roomData, action):
|
||||
rank = action.rank
|
||||
if action.container == "inventory":
|
||||
items = roomData.getComponent(obj, Inventory).items
|
||||
elif action.container == "equipment":
|
||||
items = [val for key, val in sorted(roomData.getComponent(obj, Equipment).slots.items())]
|
||||
if rank is None:
|
||||
rank = 0
|
||||
if rank not in range(len(items)):
|
||||
return
|
||||
item = items[rank]
|
||||
onUse = roomData.getComponent(item, Item).onUse
|
||||
for component in onUse:
|
||||
roomData.addComponent(item, component)
|
||||
roomData.addComponent(item, UseMessage(obj))
|
||||
|
||||
def do_interact(obj, roomData, action):
|
||||
directions = action.directions
|
||||
objects = _getNearbyObjects(obj, directions)
|
||||
for other in objects:
|
||||
if roomData.getComponent(other, Interact) is not None:
|
||||
for component in roomData.getComponent(other, Interact).components:
|
||||
roomData.addComponent(other, component)
|
||||
roomData.addComponent(other, UseMessage(obj, action.parameter))
|
||||
break
|
||||
|
||||
def do_attack(obj, roomData, action):
|
||||
directions = action.directions
|
||||
objects = _getNearbyObjects(obj, directions)
|
||||
if roomData.getComponent(obj, Input).target in objects:
|
||||
objects = {roomData.getComponent(obj, Input).target}
|
||||
fighter = roomData.getComponent(obj, Fighter)
|
||||
alignment = roomData.getComponent(obj, Faction) or Faction.NONE
|
||||
for other in objects:
|
||||
if fighter.inRange(obj, other) and alignment.isEnemy(roomData.getComponent(other, Faction) or Faction.NONE) and roomData.getComponent(other, Attackable):
|
||||
fighter.target = other
|
||||
roomData.getComponent(obj, Input).target = other
|
||||
break
|
||||
|
||||
def do_say(obj, roomData, action):
|
||||
text = action.text
|
||||
roomData.makeSound(obj, text)
|
||||
|
||||
def _getNearbyObjects(obj, directions):
|
||||
nearPlaces = obj.getGround().getNeighbours()
|
||||
objects = []
|
||||
for direction in directions:
|
||||
if direction is None:
|
||||
objects += obj.getNearObjects()
|
||||
elif isinstance(direction, str) and direction in nearPlaces:
|
||||
objects += nearPlaces[direction].getObjs()
|
||||
return objects
|
||||
|
||||
|
||||
handlers = {
|
||||
"move": do_move,
|
||||
"take": do_take,
|
||||
"drop": do_drop,
|
||||
"use": do_use,
|
||||
"interact": do_interact,
|
||||
"attack": do_attack,
|
||||
"say": do_say
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
from ..system import system
|
||||
from .. import gameobjects
|
||||
|
||||
from ..datacomponents import Create
|
||||
|
||||
@system([Create])
|
||||
def create(obj, roomData, createmessages):
|
||||
for create in createmessages:
|
||||
for template in create.templates:
|
||||
created = gameobjects.buildEntity(template, roomData, preserve=True)
|
||||
created.place(obj.getGround())
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
from ..system import system
|
||||
from .. import gameobjects
|
||||
|
||||
from ..datacomponents import Loot, LootMessage
|
||||
from ..template import Template
|
||||
|
||||
@system([LootMessage, Loot])
|
||||
def droploot(obj, roomData, _message, loot):
|
||||
for template in loot.pick():
|
||||
dropped = gameobjects.buildEntity(template, roomData, preserve=True)
|
||||
dropped.place(obj.getGround())
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
from ..system import system
|
||||
from ..datacomponents import UseMessage, Food, Attackable, Remove, Inventory
|
||||
|
||||
@system([UseMessage, Food])
|
||||
def eat(obj, roomData, use, food):
|
||||
actor = use[0].actor
|
||||
life = roomData.getComponent(actor, Attackable)
|
||||
if life is not None:
|
||||
life.heal(food.healing, obj)
|
||||
roomData.addComponent(obj, Remove)
|
||||
inv = roomData.getComponent(actor, Inventory)
|
||||
if inv is not None:
|
||||
inv.items.remove(obj)
|
||||
actor.trigger("inventorychange")
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
from ..system import system
|
||||
from ..datacomponents import UseMessage, Equippable, Equipment, Inventory
|
||||
|
||||
@system([UseMessage, Equippable])
|
||||
def equip(obj, roomData, use, equippable):
|
||||
actor = use[0].actor
|
||||
equipment = roomData.getComponent(actor, Equipment)
|
||||
inv = roomData.getComponent(actor, Inventory)
|
||||
if equipment is None or equippable.slot not in equipment.slots or inv is None:
|
||||
raise Exception("attempting to equip whithout having inventory", equippable.slot, str(equipment.slots))
|
||||
if obj in inv.items:
|
||||
|
||||
inv.items.remove(obj)
|
||||
olditem = equipment.slots[equippable.slot]
|
||||
if olditem is not None:
|
||||
inv.add(olditem)
|
||||
equipment.slots[equippable.slot] = obj
|
||||
elif equipment.slots[equippable.slot] == obj:
|
||||
if len(inv.items) < inv.capacity:
|
||||
equipment.slots[equippable.slot] = None
|
||||
inv.add(obj)
|
||||
else:
|
||||
raise Exception("attempting to equip item not in inventory")
|
||||
actor.trigger("inventorychange")
|
||||
actor.trigger("equipmentchange")
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
from ..system import system
|
||||
from ..datacomponents import Exchanger, Inventory, UseMessage, Listen, Remove
|
||||
from ..template import Template
|
||||
from .. import gameobjects
|
||||
|
||||
@system([UseMessage, Exchanger])
|
||||
def exchange(obj, roomData, usemessages, exchanger):
|
||||
for use in usemessages:
|
||||
exchange = exchanger.options.get(use.parameter)
|
||||
if exchange is None:
|
||||
# give actor a list of options
|
||||
tell_options(obj, exchanger, roomData.getComponent(use.actor, Listen))
|
||||
else:
|
||||
# perform the exchange
|
||||
perform_exchange(exchange, roomData.getComponent(use.actor, Inventory), roomData)
|
||||
use.actor.trigger("inventorychange")
|
||||
|
||||
|
||||
def tell_options(source, exchanger, ear):
|
||||
if ear is None:
|
||||
return
|
||||
if exchanger.description:
|
||||
ear.sounds.append((source, exchanger.description))
|
||||
for option in exchanger.options.values():
|
||||
ear.sounds.append((source, "{}: {}".format(option.name, option.description)))
|
||||
|
||||
|
||||
def perform_exchange(exchange, inventory, roomData):
|
||||
if inventory is None:
|
||||
return
|
||||
costs = list(exchange.costs)
|
||||
toRemove = []
|
||||
for item in inventory.items:
|
||||
# get all the items to remove
|
||||
if item.name in costs:
|
||||
toRemove.append(item)
|
||||
costs.remove(item.name)
|
||||
if len(costs):
|
||||
# not all costs can be covered; other party can't afford trade
|
||||
return
|
||||
toAdd = [gameobjects.buildEntity(Template(product), roomData) for product in exchange.products]
|
||||
if len(inventory.items) - len(toRemove) + len(toAdd) > inventory.capacity:
|
||||
return
|
||||
|
||||
for item in toRemove:
|
||||
inventory.items.remove(item)
|
||||
roomData.addComponent(item, Remove)
|
||||
for item in toAdd:
|
||||
inventory.add(item)
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
|
||||
from ..system import system
|
||||
from ..datacomponents import Fighter, Attackable, Equipment
|
||||
|
||||
@system([Fighter])
|
||||
def fight(obj, roomData, fighter):
|
||||
|
||||
other = fighter.target
|
||||
if other is None:
|
||||
return
|
||||
otherFighter = roomData.getComponent(other, Attackable)
|
||||
if otherFighter is not None and fighter.inRange(obj, other) and fighter.attackReady < roomData.getStamp():
|
||||
strength = fighter.strength
|
||||
equipment = roomData.getComponent(obj, Equipment)
|
||||
if equipment is not None:
|
||||
strength += equipment.getBonus(roomData, "strength")
|
||||
otherFighter.attack(strength, obj)
|
||||
|
||||
fighter.attackReady = roomData.getStamp() + fighter.slowness
|
||||
|
||||
fighter.target = None
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
from ..system import system
|
||||
from ..datacomponents import Events
|
||||
|
||||
@system([Events])
|
||||
def handleevents(obj, roomData, events):
|
||||
while True:
|
||||
try:
|
||||
event, args, kwargs = events.messages.popleft()
|
||||
except IndexError:
|
||||
break
|
||||
for listener in list(obj.listeners[event]):
|
||||
listener(obj, *args, **kwargs)
|
||||
|
||||
roomData.removeComponent(obj, events)
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
|
||||
from ..system import system
|
||||
from ..datacomponents import Heal, Attackable
|
||||
|
||||
@system([Heal, Attackable])
|
||||
def heal(obj, roomData, healing, attackable):
|
||||
|
||||
if not attackable.healthFull():
|
||||
if healing.nextHeal is not None and roomData.stepStamp > healing.nextHeal:
|
||||
attackable.heal(healing.amount, None)
|
||||
healing.nextHeal = roomData.getStamp() + healing.interval
|
||||
if healing.nextHeal is None:
|
||||
healing.nextHeal = roomData.getStamp() + healing.interval
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
|
||||
from ..system import system
|
||||
from ..datacomponents import Move, EnterMessage
|
||||
|
||||
@system([Move])
|
||||
def move(obj, roomData, movable):
|
||||
|
||||
neighbours = obj.getGround().getNeighbours()
|
||||
if movable.canMove(obj, movable.direction) and roomData.getStamp() > movable.moveReady:
|
||||
newPlace = neighbours[movable.direction]
|
||||
|
||||
if newPlace.accessible():
|
||||
for resident in newPlace.getObjs():
|
||||
roomData.addComponent(resident, EnterMessage(obj))
|
||||
obj.place(newPlace)
|
||||
movable.moveReady = roomData.getStamp() + movable.slowness
|
||||
obj.trigger("move")
|
||||
|
||||
movable.direction = None
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
from ..system import system
|
||||
from ..datacomponents import Remove
|
||||
|
||||
@system([Remove])
|
||||
def remove(obj, roomData, *_args):
|
||||
obj.doRemove()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue