88from entyty import _AbstractEntity as AbstractEntity
99
1010import getch
11- import pyglet .window as _window
1211import pymunk
1312from pymunk import Vec2d
1413
1514from dicepy import Roll as _Roll
1615from dicepy import Die as _Die
1716from .character import *
18- from CharActor . _objects import Armory , Goods
17+ from CharObj import Armory , Goods , Weapon , Armor , ItemStack
1918from CharActor ._charactor .dicts import load_dict
2019
21- from CharActor import log , _objects
20+ from CharActor import log
2221
2322_ability_rolls = _Roll .ability_rolls
2423
25- _key = _window .key
26-
27- _MOVE_KEYS = [_key .W , _key .D , _key .S , _key .A ]
28-
2924_LEVELS = load_dict ('levels' )
3025
3126d20 = _Die .d20 ()
@@ -161,12 +156,12 @@ def __init__(
161156 def character_sheet (self ):
162157 def of_equipment ():
163158 for piece in self .inventory .equipment ._slots .values ():
164- if piece is not None and not isinstance (piece , _objects . Weapon ):
159+ if piece is not None and not isinstance (piece , Weapon ):
165160 yield piece
166161
167162 def of_items ():
168163 for item in self .inventory .items :
169- if item is not None and not (isinstance (item , (_objects . Weapon , _objects . Armor ))):
164+ if item is not None and not (isinstance (item , (Weapon , Armor ))):
170165 yield item
171166
172167 def tab (tabs = 1 ):
@@ -505,11 +500,11 @@ def _level_up(self):
505500 self .level += 1
506501
507502 def __repr__ (self ):
508- # return f'A level {self.level}, {self._race.title} {self._role.title} named {self.name}\nLocation: Cell -> {
509- # self._cell_name}@{self._position}\n\n{self.Strength}\tInitiative: {self.initiative}\n{self.Dexterity}\tHP:
510- # {self.hp}\n{self.Constitution}\tAC: {self.armor_class}\n{self.Intelligence}\n{self.Wisdom}\n{
511- # self.Charisma}\n\n'
512- return f'{ self .name } , { self . _race . title } { self . _role .title } '
503+ # # return f'A level {self.level}, {self._race.title} {self._role.title} named {self.name}\nLocation: Cell -> {
504+ # # self._cell_name}@{self._position}\n\n{self.Strength}\tInitiative: {self.initiative}\n{self.Dexterity}\tHP:
505+ # # {self.hp}\n{self.Constitution}\tAC: {self.armor_class}\n{self.Intelligence}\n{self.Wisdom}\n{
506+ # # self.Charisma}\n\n'
507+ return f'{ self ._race .title } '
513508
514509 # def _draw(self):
515510 # self._shape.draw()
@@ -597,7 +592,28 @@ def __init__(self, name, background: str, alignment: str, grid=None):
597592 self ._action_history = []
598593 self ._is_turn = False
599594 self ._nearby_items = {}
595+ self ._nearby_units = {}
596+ self ._nearby_other = {}
600597 self ._target = None
598+
599+ def __json__ (self ):
600+ _dict = {}
601+ for key , value in self .__dict__ .copy ().items ():
602+ if hasattr (value , '__json__' ):
603+ _dict [key ] = value .__json__ ()
604+ elif key in [
605+ 'Strength' , 'Dexterity' , 'Constitution' , 'Intelligence' , 'Wisdom' , 'Charisma' , 'Barbarian' , 'Bard' , 'Cleric' ,
606+ 'Druid' , 'Fighter' , 'Monk' , 'Paladin' , 'Ranger' , 'Rogue' , 'Sorcerer' , 'Warlock' , 'Wizard' , 'Human' , 'Dwarf' ,
607+ 'Elf' , 'Gnome' , 'Half-Elf' , 'Half-Orc' , 'Halfling' , 'Tiefling' , 'LawfulGood' , 'Lawful' , 'LawfulEvil' , 'Good' ,
608+ 'TrueNeutral' , 'Evil' , 'ChaoticGood' , 'Chaotic' , 'ChaoticEvil' , 'Unaligned' , 'Acolyte' , 'Charlatan' ,
609+ 'Criminal' , 'Entertainer' , 'FolkHero' , 'GuildArtisan' , 'Hermit' , 'Noble' , 'Outlander' , 'Sage' , 'Sailor' ,
610+ 'Soldier' , 'Urchin' , '_skills' , 'inventory' , 'skillbook' , '_abilities' , '_grid' , '_grid_entity' , '_actions' ,
611+ '_action_history' , '_nearby_items' , '_nearby_units' , '_nearby_other' , '_target'
612+ ]:
613+ continue
614+ else :
615+ _dict [key ] = value
616+ return _dict
601617
602618 def _create_properties (self ):
603619 """Called when the character is added to a grid. Creates the relevant properties and makes them accessible to
@@ -624,8 +640,8 @@ def _create_properties(self):
624640 setattr (self .__class__ , 'grid_entity' , property (lambda self : self ._grid_entity ))
625641 properties = {
626642 'movements' : self .grid_entity .movements ,
627- 'movements_remaining' : self .grid_entity .movements_remaining ,
628643 'movement_queue' : self .grid_entity .movement_queue ,
644+ 'movement_energy' : self .grid_entity .movement_energy ,
629645 'cell' : self .grid_entity .cell ,
630646 'cell_name' : self .grid_entity .cell_name ,
631647 'cell_history' : self .grid_entity .cell_history ,
@@ -640,7 +656,7 @@ def _create_properties(self):
640656 setattr (self .__class__ , attr , property (lambda self , attr = attr : getattr (self .grid_entity , attr )))
641657 self ._push_handlers (on_turn_end = self .grid_entity .end_turn )
642658 else :
643- for attr in ['movements' , 'movements_remaining ' , 'movement_queue ' , 'cell' , 'cell_name' , 'cell_history' , 'last_cell' , 'x' , 'y' , 'position' , 'path' ]:
659+ for attr in ['movements' , 'movement_queue ' , 'movement_energy ' , 'cell' , 'cell_name' , 'cell_history' , 'last_cell' , 'x' , 'y' , 'position' , 'path' ]:
644660 delattr (self , attr )
645661
646662 @property
@@ -650,7 +666,7 @@ def actions(self) -> dict[str, dict[str, Any]]:
650666 @actions .setter
651667 def actions (self , actions : _Optional [dict [str , dict [str , Any ]]] = None ):
652668 if actions is None :
653- if self .grid_entity is not None :
669+ if hasattr ( self , 'grid_entity' ) and self .grid_entity is not None :
654670 actions = {
655671 'move' : self .grid_entity .actions ['move' ],
656672 'attack' : {'target' : None , 'weapon' : None , 'result' : None },
@@ -659,7 +675,10 @@ def actions(self, actions: _Optional[dict[str, dict[str, Any]]] = None):
659675 self ._actions = actions
660676 return
661677 actions = {'move' : {},
662- 'attack' : {'target' : None , 'weapon' : None , 'result' : None }, 'free' : [], 'quick' : []}
678+ 'attack' : {'target' : None , 'weapon' : None , 'result' : None },
679+ 'free' : [],
680+ 'quick' : []}
681+ self ._actions = actions
663682 return
664683 self ._actions = actions
665684
@@ -681,7 +700,8 @@ def _on_turn_change(self, actor):
681700
682701 def end_turn (self ):
683702 self ._action_history .append (self .actions )
684- self .grid_entity .end_turn ()
703+ if hasattr (self , 'grid_entity' ):
704+ self .grid_entity .end_turn ()
685705 self .actions = None
686706
687707 def _join_grid (self , grid ):
@@ -702,25 +722,44 @@ def will_save(self, dc):
702722 def _set_target (self , target ):
703723 self ._target = target
704724
705- def move (self , direction ):
706- if direction in ['north_west' , 'north' , 'north_east' , 'east' , 'south_east' , 'south' , 'south_west' , 'west' ]:
707- mvmnt_index = self .grid_entity .move_in_direction (direction )
708- self .actions ['move' ] = self .grid_entity .actions ['move' ]
709- if mvmnt_index is not None :
710- return f'{ self .actions ["move" ][mvmnt_index ]["from" ]} --> { self .actions ["move" ][mvmnt_index ]["to" ]} '
711- else :
712- return f'Cannot move { direction } .'
725+ def move (self , direction : str = None , cell : object | str = None ):
726+ FROM = self .cell .designation
727+ if cell is not None :
728+ if isinstance (cell , str ):
729+ cell = self .grid [cell ]
730+ self .grid_entity .move (cell , teleport = True )
731+ return
732+ if direction is not None and direction in {
733+ 'north_west' ,
734+ 'north' ,
735+ 'north_east' ,
736+ 'east' ,
737+ 'south_east' ,
738+ 'south' ,
739+ 'south_west' ,
740+ 'west' ,
741+ }:
742+ return self ._extracted_from_move_18 (direction , FROM )
743+
744+ # TODO Rename this here and in `move`
745+ def _extracted_from_move_18 (self , direction , FROM ):
746+ move = self .grid_entity .move_in_direction (direction )
747+ TO = self .cell .designation
748+ if not move :
749+ return move
750+ self .actions ['move' ] = self .grid_entity .actions ['move' ]
751+ self ._update_nearby ()
752+ return f'{ FROM } --> { TO } '
713753
714754 def attack (self ):
715- if 'attack' in self ._actions .keys ():
716- print ('Already attacked this turn.' )
717- return
718- if self ._target is None :
719- print ('No target.' )
720- return
721- if self .grid .get_distance (self .cell_name , self ._target .cell_name ) > self .inventory .equipment ['MAIN_HAND' ].range :
722- print ('Target out of range.' )
723- return
755+ if self .actions ['attack' ] != {'target' : None , 'weapon' : None , 'result' : None }:
756+ return 'You have already attacked this turn.'
757+ elif self ._target is None :
758+ return 'No target.'
759+ elif hasattr (self , 'grid' ) and self .grid is not None and self .grid .get_distance (self .cell_name , self ._target .cell_name ) > self .inventory .equipment ['MAIN_HAND' ].range :
760+ return 'Target out of range.'
761+ else :
762+ log (f'{ self .name } and { self .target .name } do not inhabit a grid, so attack is emulated. Combatants are assumed to be within range of each other.' )
724763 result = self ._attack_figure ()
725764 print (result )
726765 self .actions ['attack' ] = {'target' : self ._target , 'weapon' : self .inventory .equipment ['MAIN_HAND' ],
@@ -729,13 +768,22 @@ def attack(self):
729768 def _attack_figure (self ):
730769 attack_roll = d20 .roll ()
731770 if attack_roll == 20 :
732- damage = self .inventory .equipment ['MAIN_HAND' ].damage .roll () * 2
771+ damage = sum (
772+ self .inventory .equipment ['MAIN_HAND' ].damage [1 ].roll ()
773+ for _ in range (self .inventory .equipment ['MAIN_HAND' ].damage [0 ])
774+ )
775+ damage += self .Strength .modifier
776+ damage *= 2
733777 self ._target .hp -= damage
734778 return f'Critical hit! { damage } damage dealt.'
735779 elif attack_roll == 1 :
736780 return 'Critical miss!'
737781 elif attack_roll + self .Strength .modifier >= self ._target .armor_class :
738- damage = self .inventory .equipment ['MAIN_HAND' ].damage .roll () + self .Strength .modifier
782+ damage = sum (
783+ self .inventory .equipment ['MAIN_HAND' ].damage [1 ].roll ()
784+ for _ in range (self .inventory .equipment ['MAIN_HAND' ].damage [0 ])
785+ )
786+ damage += self .Strength .modifier
739787 self ._target .hp -= damage
740788 return f'{ damage } damage dealt.'
741789 else :
@@ -765,35 +813,80 @@ def _is_in_sight(self, other):
765813 def _is_in_view (self , other ):
766814 return self ._get_distance (other ) <= 40
767815
768- def _see_item (self , item ):
769- direction = get_direction (item .position , self .position )
770- if self ._is_in_pickup_range (item ):
771- self ._nearby_items [item .name ] = item
772- print (f'You see { repr (item )} at your feet.' )
773- else :
774- print (f'You see { repr (item )} to the { direction } ' )
775-
776- def look_around (self ):
816+ def _see_item (self , item , direction ):
817+ CARDINAL_DIRECTIONS = ['East' , 'North-East' , 'North' , 'North-West' , 'West' , 'South-West' , 'South' , 'South-East' ]
818+ direction = direction .split () if len (direction ) > 1 else [direction ]
819+ for d in range (len (direction )):
820+ if direction [d ] == 'S' :
821+ direction [d ] = 'South'
822+ elif direction [d ] == 'N' :
823+ direction [d ] = 'North'
824+ elif direction [d ] == 'E' :
825+ direction [d ] = 'East'
826+ elif direction [d ] == 'W' :
827+ direction [d ] = 'West'
828+ direction = '-' .join (direction ) if len (direction ) > 1 else direction [0 ]
829+ dindx = CARDINAL_DIRECTIONS .index (direction )
830+ Dist = self .grid .get_distance (self .cell_name , item .cell_name , 'cells' )
831+ if not self ._is_in_pickup_range (item ):
832+ return f'You see { repr (item )} to the { direction } '
833+ self ._nearby_items [item .name ] = item
834+ From = (dindx + 4 ) % 8
835+ view = item .__view__ ((From , Dist ))
836+ return f'You see { view } at your feet.'
837+
838+
839+
840+ def _update_nearby (self ):
777841 vision = self .vision // 5
842+ nearby_items = {}
843+ nearby_units = {}
778844 for cell in self .grid .get_area (self .cell_name , vision ):
779845 if cell .entry_object ['items' ] is not None :
780- self . _nearby_items |= cell .entry_object ['items' ]
846+ nearby_items |= cell .entry_object ['items' ]
781847 if cell .entry_unit ['players' ] is not None :
782- for player in cell .entry_unit ['players' ].values ():
783- if player .parent != self :
784- print (f'You see a { player .parent ._race .title ()} { player .parent ._role .title ()} nearby.' )
848+ nearby_units |= cell .entry_unit ['players' ]
849+ self ._nearby_items = nearby_items
850+ self ._nearby_units = nearby_units
851+
852+ def look_around (self ):
853+ self ._update_nearby ()
785854
786855 directions = {
787856 self .grid .get_direction (self .cell , item .cell )
788857 for name , item in self ._nearby_items .items ()
789858 }
790- if len (self ._nearby_items ) > 8 or len (directions ) > 4 :
791- print ('There are several objects scattered around you,' )
792- elif len (self ._nearby_items ) < 4 :
793- for item in self ._nearby_items .values ():
794- self ._see_item (item )
795-
796859
860+ if len (self ._nearby_items ) > 8 :
861+ return 'There are several objects scattered around you,'
862+ elif len (self ._nearby_items ) <= 7 :
863+ return {self ._see_item (item , direction ) for item , direction in zip (self ._nearby_items .values (), directions )}
864+
865+ def look_to (self , direction ):
866+ self ._update_nearby ()
867+ items_in_direction = []
868+ units_in_direction = []
869+ for item in self ._nearby_items .values ():
870+ d = self .grid .get_direction (self .cell , item .cell )
871+ if '-' in direction :
872+ ds = direction .split ('-' )
873+ ds .append (direction )
874+ if d in ds :
875+ items_in_direction .append (item )
876+ elif d .startswith (direction ) or d .endswith (direction ):
877+ items_in_direction .append (item )
878+ for unit in self ._nearby_units .values ():
879+ d = self .grid .get_direction (self .cell , unit .cell )
880+ if '-' in direction :
881+ ds = direction .split ('-' )
882+ ds .append (direction )
883+ if d in ds :
884+ units_in_direction .append (unit )
885+ in_direction = items_in_direction + units_in_direction
886+ for att in in_direction :
887+ self ._see_item (att , direction )
888+
889+
797890 # for item in self.grid.armory._grid_instances.values():
798891 # item_count += 1 if self._is_in_sight(item) else 0
799892 # for item in self.grid.goods._grid_instances.values():
0 commit comments