diff --git a/README.md b/README.md index 453510b..1652fcd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Each time you start a run, a genre is chosen from a predefined list of **Dimensi Some recurring elements will exist in all dimensions to provide some stability, but will often be warped to fit - for example, if the "fantasy" dimension has Cid, the dwarven shopkeeper, the "scifi" dimension has C.I.D., the Customer Inventory Droid. -Here's a few potential options, which will absolutely change over time: +Here's a few potential options, which absolutely will change over time: * Sword and Sorcery (LotR, D&D) * GitS/Neuromancer/Digitized Minds @@ -20,7 +20,6 @@ Here's a few potential options, which will absolutely change over time: * Stargates/Sliders * Isekai Protag Syndrome * Gunslingers (Wild West) -* Zombies/Undead Contagion Plague that infects other dimensions ## Links And Resources diff --git a/dev-notes.md b/dev-notes.md index 9cdafdb..1dfd166 100644 --- a/dev-notes.md +++ b/dev-notes.md @@ -1,4 +1,10 @@ -# Dev Notes, Concepts, and Ideas +# Dev Notes + +This file is a kind of scratch-pad for a multitude of ideas, that I can't implement yet. They'll be added to, refined or removed over time, so don't get too attached. + +TODO: Plan out each confirmed dimension. + +## Concepts and Ideas Color-based effects or biomes that only have an impact when in the FOV? The "obscurity" is a random value set at the beginning of a run, and lost upon death. The game will have various secrets, some of which are only accessible via certain obscurity values. @@ -10,10 +16,10 @@ It could be interesting, not necssarily a good or fun idea, but if you had another "point" or currency. Where if you spend the first one you get some kind of "cursed knowledge" point and that affects the run somehow? So the more you use your forbidden knowledge the more weird things get? If you use IRL time and date as a mechanic, go big or go home. Maybe the horror dimension is only accessible during full/new moons? Should the selected dimension be known at the start of a run? + Could have "modifiers" that apply to existing demensions, altering how they generate i.e. a zombie virus could spread. + Could have "travellers" that move from one dimension to another i.e. want a particularly powerful item? Better hope the "merchant ship" is in this world. -## Healing - -While not necessarily "realistic" some are more fun or interesting. +## Healing Items potion of healing / greater healing / full healing nano cells (nanophage) @@ -30,9 +36,7 @@ While not necessarily "realistic" some are more fun or interesting. ## Melee Weapons -As some settings are very similar you will see similar or even repeated items. Some can be interchangeable without too much trouble. - -Weapons are present here are common / uncommon - rare / God like - super experimental one of a kind. +Weapons are present here are common / uncommon - rare / God like - super experimental one of a kind. 1. sword / club / axe 2. Blessed / holy Weapons / mater worked / ancestors Weapons ect. @@ -68,9 +72,7 @@ Weapons are present here are common / uncommon - rare / God like - super experim ## Ranged Weapons -As some settings are very similar you will see similar or even repeated items. Some can be interchangeable without too much trouble. - -Weapons are present here are common / uncommon - rare / God like - super experimental one of a kind. +Weapons are present here are common / uncommon - rare / God like - super experimental one of a kind. 1. Bow / cross bow / sling / javelins ect. 2. Magically enchanted weapons and or ammunition / holy / master worked / Ancestors. @@ -80,7 +82,6 @@ Weapons are present here are common / uncommon - rare / God like - super experim 2. Branded Gucci guns / Military grade / bespoke special editions / specifically designed ammo (armour piecing / hollow point ect.) 3. Tesla hyper beam cannon / experimental thematic manipulation ray / gravidic conversion stream / anti reality fabric hacker / spontaneous combustion field / magnetic ion atom smasher. - 1. Common crystal construction / home made / fragile. 2. Antique construction of past generations / sturdy construction / multiple firing chambers / more exotic and rare earth minerals. 3. Pure untarnished faultless crystal or gem construction / soul gem that contains ally or enemy soul / pure unstable quantum core power supply. @@ -89,8 +90,65 @@ Weapons are present here are common / uncommon - rare / God like - super experim 2. Old world weapons / new condition or recently recovered / mint condition or cannibalised guns rifles ect. Specially designed ammunition. 3. Pre war experimental weapons (see cyberpunk) + 1. Standard pistol / hidden dart / watch laser ( low power) + 2. Military grade pistol / shotgun / machine gun / well maintained / polished / laser watch (med power) + 3. One of a kind Q weapons / Anti material rife with special ammunition. / multi barrel rocket launcher with programed ammunition. Laser watch (high) + + 1. Ref: 1 standard fantasy with Greek twist. Eg. + 2. Falcata (sword) + 3. Javelins of zues. Club of hercules ect. + + 1. Old / rusty / poor construction / poorly maintained - fire arms. + 2. Well maintained / military grade / new / perfect condition / blessed elephant gun / Tommy gun / crank handle gattling gun. + 3. Elder brain warp crystal ammunition / elder God touched ray gun / liminal reality beam conducton array /Finger of unsherlubb' the deep one. + + 1. Old / dirty / early generation firearms + 2. Standard military grade / extra magazine / upgrades (sites / pistol grip / optics) spec ammo / extended magazine. + 3. Cutting edge technology / one of a kind experimental weapons/ goa 'uld serpent pistol "zat" (pair / duel ) staff of light (moct toe) + + Heavy moct toe (requires tripod of very high strength) + + 1. Ref: 1 Standard fantasy. + + 1. Old / rusty / poor construction / poorly maintained - fire arms. + 2. Well maintained / military grade / new / perfect condition / blessed/ repeating / blessed tomahawk / crank handle gattling gun. + 3. Vatican relic / totem of the tribe / man portable cannon with special ammunition. + +## Armour + + 1. Leather / light / natural. + 2. Medium / metal / chain. + 3. Full plate / dragon hide / God touched. + + 1. Stab resist / metal / last generation. + 2. Bullet resistant / Stab proof / ceramic plate / Gucci brand. + 3. Sub dermal nano plates / white platelet turbo generator / lizard DNA regeneration injection / kenetic absorption and focal tech. + + 1. Leather / flawed crystal construction / battle damaged / silk mix. + 2. Pure silk / Exotic rare crystal construction / master crafted. + 3. Amulet of the core mother / light shield energy projector for the fall / sentient orb of protection (sep) + + 1. Rusty / old / poor construction. + 2. Pre war / military grade / mint condition. + 3. Power armour / stealth suit / Tesla lighting attraction plate. + + See 1 generic fantasy. + + 1. Stab resist / metal / last generation. + 2. Bullet resistant / Stab proof / ceramic plate / Gucci brand. + 3. One of a kind Q armour / watch of bullet reputation / attraction / " golden eye" EMP emitter. + + 1. Leather / light / natural. + 2. Medium / metal / bronze. + 3. Zues blessed / shield of reflection / lion of nemmedia cloak. + + 1. 1. Leather / light / natural. Silk mix. + 2. Medium / metal / pure silk. + 3. Full plate heirloom construct / Amulet of otrallort / slime of the elder beast / mirror of the ever changing staircase. + ## Narrative Goals 1. Defeat villian / kill all x in area. 2. Collection of item(s) - 3. Activation or deactivate grid / power supply / ritual. \ No newline at end of file + 3. Activation or deactivate grid / power supply / ritual. + diff --git a/source/actions.py b/source/actions.py index d83dc30..888ac32 100644 --- a/source/actions.py +++ b/source/actions.py @@ -88,14 +88,14 @@ class MeleeAction(BaseAction): #calculate message output engine: Engine = self.entity.floor_map.engine - msg_text = f"{self.entity.name} attacked {target.name}" + msg: str = f"{self.entity.name} attacked {target.name}" if damage > 0: - msg_text += f" for {damage} damage" + msg += f" for {damage} damage" else: - msg_text += f" but was ineffective" + msg += f" but was ineffective" - engine.message_log.add_message(text = msg_text) + engine.message_log.add_message(msg) #performing the actual change here, so the player's death event is at the bottom of the message log target.stats.current_hp -= damage @@ -128,24 +128,26 @@ class PickupAction(BaseAction): floor_map: FloorMap = self.entity.floor_map engine: Engine = floor_map.engine - item_stack: List[Entity] = floor_map.get_all_entities_at(x, y, items_only=True) + item_pile: List[Entity] = floor_map.get_all_entities_at(x, y, items_only=True) - if len(item_stack) == 0: + if len(item_pile) == 0: return False - elif len(item_stack) == 1: - msg = "you picked up a(n) {item_stack[0].name}" - if item_stack[0].useable.current_stack > 1: - msg = msg = f"you picked up a stack of {item_stack[0].useable.current_stack} {item_stack[0].name}" + elif len(item_pile) == 1: + item: Entity = item_pile.pop() - floor_map.entities.remove(item_stack[0]) - self.entity.inventory.insert(item_stack[0]) + msg: str = f"you picked up a(n) {item.name}" + if item.useable.current_stack > 1: + msg = f"you picked up a stack of {item.useable.current_stack} {item.name}" + + floor_map.entities.remove(item) + self.entity.inventory.insert(item) engine.message_log.add_message(msg, color=colors.terminal_light) else: from event_handlers import OptionSelector #circular imports are a pain #build an options list options: List[str] = [] - for item in item_stack: + for item in item_pile: options.append(item.name) engine.event_handler = OptionSelector( @@ -153,16 +155,16 @@ class PickupAction(BaseAction): parent_handler=engine.event_handler, title="Pick Up Item", options=options, - callback=lambda x: self.pickup_callback(engine, floor_map, self.entity, item_stack[x]) + callback=lambda x: self.pickup_callback(engine, floor_map, self.entity, item_pile[x]) ) return True #utils def pickup_callback(self, engine: Engine, floor_map: FloorMap, entity: Entity, item: Entity) -> None: - msg = "you picked up a(n) {item.name}" + msg: str = f"you picked up a(n) {item.name}" if item.useable.current_stack > 1: - msg = msg = f"you picked up a stack of {item.useable.current_stack} {item.name}" + msg = f"you picked up a stack of {item.useable.current_stack} {item.name}" floor_map.entities.remove(item) entity.inventory.insert(item) @@ -192,12 +194,17 @@ class DropAction(BaseAction): item.x = x item.y = y + #TODO: Check for floorpile stack merging floor_map.entities.add(item) if self.display_callback: #adjust the cursor self.display_callback(-1) - engine.message_log.add_message(f"you dropped a(n) {item.name}", color=colors.terminal_light) + msg: str = f"you dropped a(n) {item.name}" + if item.useable.current_stack > 1: + msg = f"you dropped a stack of {item.useable.current_stack} {item.name}" + + engine.message_log.add_message(msg, color=colors.terminal_light) return True @@ -225,15 +232,18 @@ class DropPartialStackAction(BaseAction): item: Entity = inventory.access(self.index) + #TODO: Check for floorpile stack merging new_item: Entity = item.spawn(x, y, floor_map) item.useable.current_stack -= self.amount new_item.useable.current_stack = self.amount if self.display_callback: #adjust the cursor - self.display_callback(-1) + self.display_callback(0) #by zero - engine.message_log.add_message(f"you dropped a stack of {new_item.useable.current_stack} {new_item.name}", color=colors.terminal_light) + msg: str = f"you dropped a partial stack of {new_item.useable.current_stack} {new_item.name}" + + engine.message_log.add_message(msg, color=colors.terminal_light) return True diff --git a/source/colors.py b/source/colors.py index a80f051..cea4140 100644 --- a/source/colors.py +++ b/source/colors.py @@ -1,14 +1,14 @@ #Standard colors -white = (0xFF, 0xFF, 0xFF) -black = (0x0, 0x0, 0x0) +white = (0xFF, 0xFF, 0xFF) +black = (0x0, 0x0, 0x0) -red = (0xFF, 0, 0) -green = (0, 0xFF, 0) -blue = (0, 0, 0xFF) +red = (0xFF, 0, 0) +green = (0, 0xFF, 0) +blue = (0, 0, 0xFF) -yellow = (0xFF, 0xFF, 0) +yellow = (0xFF, 0xFF, 0) magenta = (0xFF, 0, 0xFF) -cyan = (0, 0xFF, 0xFF) +cyan = (0, 0xFF, 0xFF) #gameboy DMG-01, according to Wikipedia's CSS gameboy_00 = (0x29, 0x41, 0x39) @@ -18,4 +18,7 @@ gameboy_03 = (0x7b, 0x82, 0x10) #terminal-like terminal_light = (200, 200, 200) -terminal_dark = (100, 100, 100) +terminal_dark = (100, 100, 100) + +#extended colors +orange = (0xFF, 0xA5, 0x00) \ No newline at end of file diff --git a/source/event_handlers.py b/source/event_handlers.py index ab1effe..13b4b1a 100644 --- a/source/event_handlers.py +++ b/source/event_handlers.py @@ -142,9 +142,17 @@ class GameplayHandler(EventHandler): if key == tcod.event.KeySym.TAB: self.engine.event_handler = InventoryViewer(self.engine, self, player) - #debugging - if key == tcod.event.KeySym.o: #TODO: remove this - self.engine.event_handler = OptionSelector(self.engine, self, options=["zero", "one", "two", "three"], callback=lambda x: print("You chose", x)) + #debugging - can hook this up to more later + if (event.mod & tcod.event.Modifier.CTRL) and key == tcod.event.KeySym.d: + self.engine.event_handler = OptionSelector( + self.engine, + self, + title="Debug Selector", + options=["Zero", "One", "Two", "Three"], + callback=lambda x: self.engine.message_log.add_message(f"DBG: You selected {x}", colors.orange), + margin_x=20, + margin_y=12, + ) def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: if self.engine.floor_map.in_bounds(event.tile.x, event.tile.y): @@ -255,10 +263,10 @@ class InventoryViewer(EventHandler): string = msg, fg=colors.terminal_light, bg=colors.black, ) - offset += 1 + offset += 2 if self.length > 0: - inner_console.print(2, 2 + self.cursor, string = ">", fg=colors.terminal_light, bg=colors.black) + inner_console.print(2, 2 + self.cursor * 2, string = ">", fg=colors.terminal_light, bg=colors.black) else: #if inventory is empty, show a default message inner_console.print_box( @@ -295,10 +303,14 @@ class InventoryViewer(EventHandler): callback = lambda x: self.stack_selector_callback(x) #TODO: drop 1, drop all for stacks - self.engine.event_handler = OptionSelector(self.engine, self, + self.engine.event_handler = OptionSelector( + self.engine, + self, title=item.name, options=options, - callback=callback + callback=callback, + margin_x=20, + margin_y=12, ) #TODO: hotkeys via a config @@ -319,7 +331,7 @@ class InventoryViewer(EventHandler): return self.drop() elif selected == 2: #Back return None #the selector does the change - + def stack_selector_callback(self, selected: int) -> Optional[BaseAction]: if selected == 0: #Use return self.use() @@ -336,7 +348,7 @@ class InventoryViewer(EventHandler): index = self.cursor return UsageAction(self.entity, index, self.entity, lambda x: self.adjust_length(x)) - + def drop_partial_stack(self, amount: int) -> Optional[BaseAction]: """Drop part of an item stack at the cursor's position, and adjust the cursor if needed.""" if self.length > 0: @@ -350,7 +362,7 @@ class InventoryViewer(EventHandler): index = self.cursor return DropAction(self.entity, index, lambda x: self.adjust_length(x)) - + def adjust_length(self, amount: int): self.length += amount if self.cursor >= self.length: @@ -408,9 +420,9 @@ class OptionSelector(EventHandler): string = option, fg=colors.terminal_light, bg=colors.black, ) - offset += 1 + offset += 2 - select_console.print(2, 2 + self.cursor, string = ">", fg=colors.terminal_light, bg=colors.black) + select_console.print(2, 2 + self.cursor * 2, string = ">", fg=colors.terminal_light, bg=colors.black) select_console.blit(console, self.margin_x, self.margin_y) diff --git a/source/inventory.py b/source/inventory.py index 9857477..d6e8053 100644 --- a/source/inventory.py +++ b/source/inventory.py @@ -16,7 +16,7 @@ class Inventory: return False #check for stacking - if item.useable.maximum_stack > 0: + if item.useable.maximum_stack > 1: if self.try_stack_merge(item): return True @@ -34,7 +34,7 @@ class Inventory: return None else: return self._contents.pop(index) - + def discard(self, index: int) -> None: if index < 0 or index >= len(self._contents): pass @@ -44,15 +44,16 @@ class Inventory: @property def contents(self) -> List[Entity]: return self._contents - + #utils def try_stack_merge(self, new_item: Entity): for item in self._contents: if item.useable.is_stack_mergable(new_item.useable): - #TODO: add a callback in the entity if other components need to be tweaked down the road + #NOTE: I'll add a callback in the entity if other components need to be tweaked down the road item.useable.current_stack += new_item.useable.current_stack new_item.useable.current_stack = 0 #just in case return True return False -#TODO: items need a weight, inventory needs a max capacity \ No newline at end of file +#TODO: items need a weight? +#TODO: inventory needs a max capacity? \ No newline at end of file diff --git a/source/useable.py b/source/useable.py index 07fe50f..da12caa 100644 --- a/source/useable.py +++ b/source/useable.py @@ -30,7 +30,7 @@ class BaseUseable: `appearance` is what the item looks like, and can be substituted into the result. """ return None #default - + #utils def reduce_stack(self, amount: int = 1) -> bool: """ @@ -45,7 +45,7 @@ class BaseUseable: def is_stack_empty(self) -> bool: return self.consumable and self.maximum_stack > 0 and self.current_stack <= 0 - + def is_stack_mergable(self, other: BaseUseable) -> bool: """ If this returns `True`, this instance can be merged with the other instance. @@ -78,7 +78,7 @@ class PotionOfHealing(BaseUseable): self.__last_roll = roll_dice(4, 4) stats.current_hp += self.__last_roll return self.reduce_stack() - + def get_used_msg(self, appearance: str) -> Optional[str]: return f"You restored {self.__last_roll} health." @@ -88,7 +88,7 @@ class PotionOfHealing(BaseUseable): # Extra Healing: 8d8 | 6d8 | 4d8. If the result is above MaxHP, MaxHP is incrased by 5 | 2 | 0. # Full Healing: 400 | 400 | 400. If the result is above MaxHP, MaxHP is incrased by 8 | 4 | 0. -#TODO: move this into a different file +#TODO: move this into a different file, 'utils.py' import random def roll_dice(number: int, sides: int) -> int: total: int = 0