diff --git a/README.md b/README.md index 501b865..e0a1a5a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,27 @@ A lot of functionality is actually working. - Fighting - NPC's +## Installation/starting instructions + +Requires python3, tested to work on at least python 3.5.2 in linux + +Because of the use of NCURSES and Unix Domain Sockets, it probably won't work on windows (will be fixed later) + +Not tested on mac. If anyone could test this for me this would be much appreciated. + +Run `hostrooms.py` from the same directory to start the server. + +Run `rooms` or `playgame.py` to start the client + +## Playing instructions + +Use the arrow keys or wasd to move around. +Use 'e' to add an item from the ground into your inventory. +Use 'q' to drop the top item in your inventory. +Use 'r' to use/interact with the top item in your inventory. +Use 'f' to attack an enemy in the same square as you. +Use WASD to attack enemies in adjacent squares. + ## Vision/ideas @@ -32,13 +53,14 @@ The idea is to make 3 different kind of areas: - procedurally generated dungeons, where players can explore and fight for loot * like roguelikes * if the player dies they can not return to the same dungeon + * group dungeons would be great too ## TODO first - better code documentation - test server robustness - make items usable -- farming +- farming (only harvest left to do) - more content (rooms, objects etc) - equipment - configurable keybindings diff --git a/client/characters.json b/client/characters.json index e1d6ba3..4b0e1a3 100644 --- a/client/characters.json +++ b/client/characters.json @@ -21,6 +21,7 @@ "goblin": "g", "seed": ":", "plant": "Y", + "youngplant": "v", " ": " " } } diff --git a/server/components/build.py b/server/components/build.py index 680b9b4..7da4a17 100644 --- a/server/components/build.py +++ b/server/components/build.py @@ -18,5 +18,5 @@ class Build: def use(self, user): obj = gameobjects.makeEntity(self.buildType, self.roomData, *self.buildArgs, **self.buildKwargs) obj.place(user.getGround()) - self.owner.remove() + self.owner.trigger("drop") diff --git a/server/components/inputcontroller.py b/server/components/inputcontroller.py index 07616f4..b87e7f3 100644 --- a/server/components/inputcontroller.py +++ b/server/components/inputcontroller.py @@ -8,6 +8,7 @@ class InputController: def attach(self, obj, roomData): self.owner = obj + self.roomData = roomData for dep in {"inventory", "move", "fighter", "alignment"}: if not obj.getComponent(dep): @@ -40,13 +41,14 @@ class InputController: if kind == "take": for obj in self.owner.getNearObjects(): if obj.getComponent("item") != None and self.inventory.canAdd(obj): + obj.remove() self.inventory.add(obj) - obj.unPlace() break if kind == "drop": for obj in self.inventory.getItems(): self.inventory.drop(obj) + obj.construct(self.roomData) obj.place(self.owner.getGround()) break diff --git a/server/components/inventory.py b/server/components/inventory.py index ebd8b7e..ebd75fc 100644 --- a/server/components/inventory.py +++ b/server/components/inventory.py @@ -20,6 +20,5 @@ class Inventory: return list(self.items) def onItemUpdate(self, item, action, *data): - if action == "remove": - print(item, action) + if action == "drop": self.drop(item) diff --git a/server/components/loot.py b/server/components/loot.py index 8675bf9..9808a8a 100644 --- a/server/components/loot.py +++ b/server/components/loot.py @@ -19,8 +19,18 @@ class Loot: def dropLoot(self, obj, action, *data): if action == "die": - for item, chance in self.items: + 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(): - # todo: args and kwargs - obj = gameobjects.makeEntity(item, self.roomData) + obj = gameobjects.makeEntity(item, self.roomData, *args, **kwargs) obj.place(self.owner.getGround()) diff --git a/server/components/portal.py b/server/components/portal.py index 6f833f6..19d45f0 100644 --- a/server/components/portal.py +++ b/server/components/portal.py @@ -7,7 +7,11 @@ class Portal: self.destRoom = destRoom self.destPos = destPos - def onEnter(self, obj): + def attach(self, obj, roomData): + obj.addListener(self.onObjEvent) - obj.trigger("changeroom", self.destRoom, self.destPos) + + def onObjEvent(self, owner, action, obj=None, *data): + if action == "objectenter": + obj.trigger("changeroom", self.destRoom, self.destPos) diff --git a/server/entity.py b/server/entity.py index cc96e78..b8a9d8e 100644 --- a/server/entity.py +++ b/server/entity.py @@ -14,7 +14,7 @@ class Entity: Remove methods are for cleanup, like unsubscribing from events. """ - def __init__(self, roomEvents, sprite=' ', solid=False, height=0, name=None, components={}): + def __init__(self, sprite=' ', solid=False, height=0, name=None, components={}): self.sprite = sprite # the name of the image to display for this entity self.solid = solid self.height = height # if multiple objects are on a square, the tallest one is drawn @@ -22,13 +22,16 @@ class Entity: self.components = components self.observable = event.Event() - for component in components.values(): - if hasattr(component, "attach"): - component.attach(self, roomEvents) - self.ground = None + self.roomData = None pass + def construct(self, roomData): + self.roomData = roomData + for component in self.components.values(): + if hasattr(component, "attach"): + component.attach(self, roomData) + def hasComponent(self, name): return name in self.components @@ -42,18 +45,15 @@ class Entity: ground.addObj(self) self.ground = ground - def unPlace(self): - + def remove(self): if self.ground: self.ground.removeObj(self) self.ground = None - - def remove(self): - self.unPlace() for component in self.components.values(): if hasattr(component, "remove"): component.remove() self.trigger("remove") + self.roomData = None def addListener(self, callback, key=None): self.observable.addListener(callback, key) diff --git a/server/gameobjects.py b/server/gameobjects.py index 05933b4..a9748e6 100644 --- a/server/gameobjects.py +++ b/server/gameobjects.py @@ -22,81 +22,92 @@ from components.build import Build entities = {} -def makeWall(roomData): - return Entity(roomData, sprite="wall", height=2, solid=True) +def makeWall(): + return Entity(sprite="wall", height=2, solid=True) entities["wall"] = makeWall -def makeRock(roomData): - return Entity(roomData, sprite="rock", height=10, solid=True) +def makeRock(): + return Entity(sprite="rock", height=10, solid=True) entities["rock"] = makeRock -def makeTree(roomData): - return Entity(roomData, sprite="tree", height=3, solid=True) +def makeTree(): + return Entity(sprite="tree", height=3, solid=True) entities["tree"] = makeTree -def makeStone(roomData): - return Entity(roomData, sprite="stone", height=0.2, components={"item": Build("wall")}) +def makeStone(): + return Entity(sprite="stone", height=0.2, components={"item": Build("wall")}) entities["stone"] = makeStone -def makePebble(roomData): - return Entity(roomData, sprite="pebble", height=0.2, components={"item": Item()}) +def makePebble(): + return Entity(sprite="pebble", height=0.2, components={"item": Item()}) entities["pebble"] = makePebble -def makeGrass(roomData): - return Entity(roomData, sprite=random.choice(["ground", "grass1", "grass2", "grass3"]), height=0.15) +def makeGrass(): + return Entity(sprite=random.choice(["ground", "grass1", "grass2", "grass3"]), height=0.1) entities["grass"] = makeGrass -def makeFloor(roomData): - return Entity(roomData, sprite="floor", height=0.1) +def makeFloor(): + return Entity(sprite="floor", height=0) entities["floor"] = makeFloor -def makeGround(roomData): - return Entity(roomData, sprite="ground", height=0.1) +def makeGround(): + return Entity(sprite="ground", height=0) entities["ground"] = makeGround -def makeWater(roomData): - return Entity(roomData, sprite="water", height=0.1, solid=True) +def makeWater(): + return Entity(sprite="water", height=0, solid=True) entities["water"] = makeWater -def makeRoomExit(roomData, destRoom, destPos=None, char="exit", size=1): - return Entity(roomData, sprite=char, height=size, components={"collision": Portal(destRoom, destPos)}) +def makeRoomExit(destRoom, destPos=None, char="exit", size=1): + return Entity(sprite=char, height=size, components={"collision": Portal(destRoom, destPos)}) entities["roomexit"] = makeRoomExit -def makeRabbit(roomData): - return Entity(roomData, sprite="rabbit", height=1, components={"move": Move(slowness=4), "controller": RandomWalkController(moveChance=0.05)}) +def makeRabbit(): + return Entity(sprite="rabbit", height=1, components={"move": Move(slowness=4), "controller": RandomWalkController(moveChance=0.05)}) entities["rabbit"] = makeRabbit -def makeDummy(roomData): - return Entity(roomData, sprite="dummy", height=1, components={"fighter": Fighter(maxHealth=20, strength=0), "alignment": Alignment(faction.EVIL)}) +def makeDummy(): + return Entity(sprite="dummy", height=1, components={"fighter": Fighter(maxHealth=20, strength=0), "alignment": Alignment(faction.EVIL)}) entities["dummy"] = makeDummy -def makeSpikeTrap(roomData): - return Entity(roomData, sprite="spikes", height=1, components={"fighter": Fighter(maxHealth=25, strength=25), "collision": Trap()}) +def makeSpikeTrap(): + return Entity(sprite="spikes", height=1, components={"fighter": Fighter(maxHealth=25, strength=25), "collision": Trap()}) entities["spiketrap"] = makeSpikeTrap -def makeGoblin(roomData): - return Entity(roomData, sprite="goblin", height=1.2, components={ +def makeGoblin(): + return Entity(sprite="goblin", height=1.2, components={ "move": Move(slowness=4), "fighter": Fighter(maxHealth=25, strength=5, slowness=3), "alignment": Alignment(faction.EVIL), "controller": MonsterAi(viewDist=5, moveChance=0.01), - "loot": Loot([("stone", .5), ("pebble", .5)]) + "loot": Loot([("seed", .5), ("seed", .1)]) }) entities["goblin"] = makeGoblin -def makeGoblinSpawner(roomData): # I should probably generalize this... - return Entity(roomData, sprite="portal", height=1, name="goblinspawner", components={"spawn": Spawner("goblin", 2, 20)}) +def makeGoblinSpawner(): # I should probably generalize this... + return Entity(sprite="portal", height=1, name="goblinspawner", components={"spawn": Spawner("goblin", 2, 20)}) entities["goblinspawner"] = makeGoblinSpawner -def makeSeed(roomData): - return Entity(roomData, sprite="seed", height=0.3, components={"grow": Growing("plant", 100)}) -entities["seed"] = makeSeed +def makeSownSeed(): + return Entity(sprite="seed", height=0.05, components={"grow": Growing("youngplant", 100)}) +entities["sownseed"] = makeSownSeed -def makePlant(roomData): - return Entity(roomData, sprite="plant", height=1.2) +def makeYoungPlant(): + return Entity(sprite="youngplant", height=0.5, components={"grow": Growing("plant", 200)}) +entities["youngplant"] = makeYoungPlant + +def makePlant(): + return Entity(sprite="plant", height=1.2) entities["plant"] = makePlant -def makeEntity(entType, *args, **kwargs): - return entities[entType](*args, **kwargs) +def makeSeed(): + return Entity(sprite="seed", height=0.3, components={"item": Build("sownseed")}) +entities["seed"] = makeSeed + + +def makeEntity(entType, roomData, *args, **kwargs): + entity = entities[entType](*args, **kwargs) + entity.construct(roomData) + return entity diff --git a/server/player.py b/server/player.py index 4132186..9e6c91c 100644 --- a/server/player.py +++ b/server/player.py @@ -6,6 +6,7 @@ from components.fighter import Fighter from components.healing import Healing from components.alignment import Alignment import faction +import entity class Player: @@ -42,7 +43,7 @@ class Player: self.leaveRoom() pos = place or room.getEntrance() - self.entity = room.makeEntity( + self.entity = entity.Entity( sprite = "player", solid = False, height = 2, @@ -55,7 +56,7 @@ class Player: "alignment": Alignment(faction.GOOD), "heal": Healing(interval=25) }) - + self.entity.construct(room.getRoomData()) self.entity.addListener(self.onPlayerAction) room.addObj(pos, self.entity) diff --git a/server/room.py b/server/room.py index 8f4ea37..d37ca13 100644 --- a/server/room.py +++ b/server/room.py @@ -48,7 +48,8 @@ class Room: kwargs = obj.get("kwargs", {}) else: continue - self.addObj((x, y), self.makeObject(objtype, *args, **kwargs)) + ent = gameobjects.makeEntity(objtype, self.roomData, *args, **kwargs) + self.addObj((x, y), ent) def getEntrance(self): @@ -87,11 +88,8 @@ class Room: return self._getGround(pos) return None - def makeObject(self, objtype, *args, **kwargs): - return gameobjects.makeEntity(objtype, self.roomData, *args, **kwargs) - - def makeEntity(self, *args, **kwargs): - return entity.Entity(self.roomData, *args, **kwargs) + def getRoomData(self): + return self.roomData def addObj(self, pos, obj): obj.place(self.get(pos)) diff --git a/server/worldgen.py b/server/worldgen.py index b942c99..0a8489d 100644 --- a/server/worldgen.py +++ b/server/worldgen.py @@ -39,7 +39,9 @@ def generateBeginRoom(): g.set(50, 25, ["grass", "rabbit", "rabbit", "rabbit", "rabbit"]) g.set(11, 12, ["grass", "dummy"]) g.set(37, 18, ["spiketrap"]) - g.set(19, 7, ["grass", "seed"]) + g.set(21, 16, ["grass", "seed"]) + g.set(21, 17, ["grass", "seed"]) + g.set(22, 16, ["grass", "seed"]) g.set(30, 20, {"type": "roomexit", "args": ["basement", "stairup"], "kwargs": {"char": "stairdown"}})