class Plate(Model): name = CharField(max_length=100, unique=True) description = TextField(max_length=4000, blank=True, null=True) picture_sets = M2M(to=PictureSet, related_name='plates', blank=True) armor_class_bonus = PositiveSmallIntegerField(blank=True, null=True) parrying = PositiveSmallIntegerField(blank=True, null=True) endurance = PositiveSmallIntegerField() weight = DecimalField(max_digits=10, decimal_places=1) mod_max_agility = PositiveSmallIntegerField(blank=True, null=True) mod_max_movement = CharField(max_length=2, blank=True, null=True) mod_pickpocketing = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_lockpicking = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_sneaking_towns = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_sneaking_wilderness = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_hiding_towns = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_hiding_wilderness = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_climbing = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) mod_traps = DecimalField(max_digits=3, decimal_places=2, blank=True, null=True) allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_plates', blank=True, ) sorting_number = DecimalField(max_digits=3, decimal_places=2) def __str__(self): return self.name def short_name(self): return rid_of_special_chars(self.name) class Meta: ordering = ['sorting_number']
class Skill(Model): name = CharField(max_length=100, unique=True) tested_trait = CharField(max_length=50, blank=True, null=True) image = ImageField(upload_to='site_features_pics', blank=True, null=True) allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_skills', blank=True, ) group = FK(to=SkillGroup, related_name='skills', on_delete=PROTECT, blank=True, null=True) types = M2M(to=SkillType, related_name='skills', blank=True) sorting_name = CharField(max_length=101, blank=True, null=True) def __str__(self): return str(self.name) def save(self, *args, **kwargs): if self.name: self.sorting_name = create_sorting_name(self.name) super().save(*args, **kwargs) def short_name(self): return rid_of_special_chars(self.name) class Meta: ordering = ['sorting_name']
class Shield(Model): name = CharField(max_length=100, unique=True) description = TextField(max_length=4000, blank=True, null=True) picture_sets = M2M(to=PictureSet, related_name='shields', blank=True) # enemies_no do skasowania enemies_no = PositiveSmallIntegerField() armor_class_bonus_close_combat = PositiveSmallIntegerField( blank=True, null=True, ) armor_class_bonus_distance_combat = PositiveSmallIntegerField( blank=True, null=True, ) weight = DecimalField(max_digits=10, decimal_places=1) allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_shields', blank=True, ) sorting_number = DecimalField(max_digits=3, decimal_places=2) def __str__(self): return self.name class Meta: ordering = ['sorting_number']
class CharacterGroup(Model): """A mnodel for storing default knowledge packet sets for groups of characters. These should be automatically added to the knowlege packets of a newly created Character that belongs to a CharacterGroup (by signals). """ name = CharField(max_length=250) author = FK( to=Profile, related_name='character_groups_authored', on_delete=SET_NULL, blank=True, null=True, ) characters = M2M(to=Character, related_name='character_groups') default_knowledge_packets = M2M( to=KnowledgePacket, related_name='character_group_defaults', blank=True, ) default_skills = M2M(to=Skill, related_name='character_group_defaults', blank=True) order_no = SmallIntegerField(default=1) class Meta: ordering = ['order_no', 'name'] unique_together = ('name', 'author') verbose_name = '* CHARACTER GROUP' verbose_name_plural = '* CHARACTER GROUPS' def __str__(self): return f"{self.name} [{self.author}]"
class Statement(Model): text = TextField() thread = FK(to=Thread, related_name='statements', on_delete=CASCADE) author = FK(to=Profile, related_name='statements', on_delete=PROTECT) image = ImageField(upload_to='post_pics', blank=True, null=True) seen_by = M2M(to=Profile, related_name='statements_seen', blank=True) created_at = DateTimeField(auto_now_add=True) # Announcement options = M2M(to=Option, related_name='threads', blank=True) class Meta: ordering = ['created_at'] def __str__(self): text = self.text return f'{text[:100]}...' if len(str(text)) > 100 else text def save(self, *args, **kwargs): first_save = True if not self.pk else False super().save(*args, **kwargs) if first_save and self.image: img = Image.open(self.image.path) if img.height > 700 or img.width > 700: output_size = (700, 700) img.thumbnail(output_size) img.save(self.image.path)
class Thread(Model): objects = ThreadManager() title = CharField(max_length=100, unique=True) kind = CharField(max_length=15, choices=THREAD_KINDS) known_directly = M2M(to=Profile, related_name='threads_known_directly') # known_directly also use instead of inform_gm in Plans created_at = DateTimeField(auto_now_add=True) # Announcement followers = M2M(to=Profile, related_name='threads_followed', blank=True) tags = M2M(to=ThreadTag, related_name='threads', blank=True) # Debate is_ended = BooleanField(default=False) # also Demands instead of is_done is_exclusive = BooleanField(default=False) class Meta: ordering = ['created_at'] def __str__(self): return self.title def informables(self): if self.kind == 'Announcement': qs = Profile.active_players.all() elif self.kind == 'Debate': qs = Profile.living.all() else: qs = Profile.objects.none() return qs.exclude(id__in=self.known_directly.all()) def get_absolute_url(self): return f'/communications/thread:{self.pk}/None/#page-bottom'
class Synergy(Model): name = CharField(max_length=100) skills = M2M(to=Skill, related_name='skills') allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_synergies', blank=True, ) sorting_name = CharField(max_length=250, blank=True, null=True) def __str__(self): return self.name def save(self, *args, **kwargs): if self.name: self.sorting_name = create_sorting_name(self.name) super().save(*args, **kwargs) def short_name(self): return rid_of_special_chars(self.name) class Meta: ordering = ['sorting_name'] verbose_name = 'Synergy' verbose_name_plural = 'Synergies'
class NewsAnswer(Model): text = TextField() news = FK(to=News, related_name='news_answers', on_delete=CASCADE) author = FK(to=Profile, related_name='news_answers', on_delete=CASCADE) image = ImageField(blank=True, null=True, upload_to='news_pics') seen_by = M2M(to=Profile, related_name='news_answers_seen', blank=True) created_at = DateTimeField( # auto_now_add=True ) survey_options = M2M(to=SurveyOption, related_name='threads', blank=True) class Meta: ordering = ['created_at'] def __str__(self): return self.text[:100] + '...' if len(str(self.text)) > 100 else self.text def save(self, *args, **kwargs): first_save = True if not self.pk else False super().save(*args, **kwargs) if first_save and self.image: img = Image.open(self.image.path) if img.height > 700 or img.width > 700: output_size = (700, 700) img.thumbnail(output_size) img.save(self.image.path)
class Character(Model): objects = CharacterManager() profile = OneToOne(to=Profile, on_delete=CASCADE) first_name = FK(to=FirstName, related_name='characters', on_delete=PROTECT, blank=True, null=True) family_name = FK(to=FamilyName, related_name='characters', on_delete=PROTECT, blank=True, null=True) cognomen = CharField(max_length=250, blank=True, null=True) description = TextField(blank=True, null=True) frequented_locations = M2M(to=Location, related_name='frequented_by_characters', blank=True) biography_packets = M2M(to=BiographyPacket, related_name='characters', blank=True) dialogue_packets = M2M(to=DialoguePacket, related_name='characters', blank=True) # USE: profile.characters_known_directly.all() # for characters that the profile knows directly known_directly = M2M(to=Profile, related_name='characters_known_directly', blank=True) known_indirectly = M2M(to=Profile, related_name='characters_known_indirectly', blank=True) sorting_name = CharField(max_length=250, blank=True, null=True) class Meta: ordering = ['sorting_name'] verbose_name = '* CHARACTER' verbose_name_plural = '* CHARACTERS' def __str__(self): name = f"{self.first_name} " if self.first_name else "" family_name = f"{self.family_name} " if self.family_name else "" cognomen = f"{self.cognomen} " if self.cognomen else "" return f"{name}{family_name}{cognomen}".strip() def save(self, *args, **kwargs): self.sorting_name = create_sorting_name(self.__str__()) super().save(*args, **kwargs) def all_known(self): return self.known_directly.all() | self.known_indirectly.all() def informables(self): qs = Profile.active_players.all() qs = qs.exclude(id__in=self.all_known()) qs = qs.exclude(character__id=self.pk) return qs
class Weapon(Model): weapon_type = FK(to=WeaponType, related_name='weapons', on_delete=PROTECT) name = CharField(max_length=100, unique=True) description = TextField(max_length=4000, blank=True, null=True) picture_sets = M2M(to=PictureSet, related_name='weapons', blank=True) # -------------------# TODO marked for removal [in 2023]------------------- delay = PositiveSmallIntegerField() damage_big_dices = CharField(max_length=10, blank=True, null=True) damage_big_add = PositiveSmallIntegerField(blank=True, null=True) # ------------------------------------------------------------------------- damage_small_dices = CharField(max_length=10, blank=True, null=True) damage_small_add = PositiveSmallIntegerField(blank=True, null=True) damage_type = CharField(max_length=10, choices=DAMAGE_TYPES) special = TextField(max_length=4000, blank=True, null=True) range = CharField(max_length=100, blank=True, null=True) size = CharField(max_length=5, choices=SIZES) trait = CharField(max_length=10, choices=TRAITS) avg_price_value = PositiveSmallIntegerField(blank=True, null=True) avg_price_currency = CharField(max_length=5, choices=CURRENCIES, blank=True, null=True) avg_weight = DecimalField(max_digits=10, decimal_places=1) allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_weapons', blank=True, ) sorting_name = CharField(max_length=250, blank=True, null=True) def __str__(self): return self.name def save(self, *args, **kwargs): if self.name: self.sorting_name = create_sorting_name(self.name) super().save(*args, **kwargs) def short_name(self): return rid_of_special_chars(self.name) def damage_summary(self): damage_small = str(self.damage_small_dices) # if self.damage_small_add: # damage_small += ('+' + str(self.damage_small_add)) # damage_big = str(self.damage_big_dices) # if self.damage_big_add: # damage_big += ('+' + str(self.damage_big_add)) damage_small_add = "" if self.damage_small_add: damage_small_add += ("+" + str(self.damage_small_add)) return f"{self.damage_small_dices}{damage_small_add}" class Meta: ordering = ['sorting_name']
class Option(Model): author = FK(to=Profile, related_name='options', on_delete=CASCADE) text = CharField(max_length=50) voters_yes = M2M(to=Profile, related_name='options_votes_yes', blank=True) voters_no = M2M(to=Profile, related_name='options_votes_no', blank=True) class Meta: ordering = ['text'] def __str__(self): text = self.text return f'{text[:100]}...' if len(str(text)) > 100 else text
class SurveyOption(Model): objects = SurveyOptionManager() author = FK(to=Profile, related_name='survey_options_authored', on_delete=CASCADE) option_text = CharField(max_length=50) yes_voters = M2M(to=Profile, related_name='survey_yes_votes', blank=True) no_voters = M2M(to=Profile, related_name='survey_no_votes', blank=True) class Meta: ordering = ['option_text'] def __str__(self): return self.option_text[:100] + '...' if len( str(self.option_text)) > 100 else self.option_text
class News(Model): objects = NewsManager() title = CharField(max_length=100, unique=True) topic = FK(to=Topic, related_name='news', on_delete=CASCADE) allowed_profiles = M2M(to=Profile, related_name='allowed_news') created_at = DateTimeField(auto_now_add=True) followers = M2M(to=Profile, related_name='followed_news', blank=True) def __str__(self): return self.title[:50] + '...' if len(str(self.title)) > 100 else self.title class Meta: verbose_name = 'News' verbose_name_plural = 'News'
class EliteKlass(Model): name = CharField(max_length=100, unique=True) elite_profession = FK( to=EliteProfession, related_name='elite_klasses', on_delete=PROTECT, ) description = TextField(max_length=4000, blank=True, null=True) start_perks = TextField(max_length=4000, blank=True, null=True) allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_elite_klasses', blank=True, ) sorting_name = CharField(max_length=250, blank=True, null=True) def __str__(self): return self.name def save(self, *args, **kwargs): if self.name: self.sorting_name = create_sorting_name(self.name) super().save(*args, **kwargs) def short_name(self): return rid_of_special_chars(self.name) class Meta: ordering = ['sorting_name'] verbose_name = 'Elite klass' verbose_name_plural = 'Elite klasses'
class SynergyLevel(Model): synergy = FK(to=Synergy, related_name='synergy_levels', on_delete=PROTECT) level = CharField(max_length=10, choices=S_LEVELS[1:]) description = TextField(max_length=4000, blank=True, null=True) perks = M2M(to=Perk, related_name='synergy_levels', blank=True) acquired_by = M2M(to=Profile, related_name='synergy_levels', blank=True) sorting_name = CharField(max_length=250, blank=True, null=True) def __str__(self): return f'{str(self.synergy.name)} [{self.level}]' def save(self, *args, **kwargs): self.sorting_name = create_sorting_name(self.__str__()) super().save(*args, **kwargs) class Meta: ordering = ['sorting_name']
class KnowledgePacket(InfoPacket): """A class for info packets that might be shared among multiple Skills.""" author = FK( to=Profile, related_name='authored_kn_packets', on_delete=PROTECT, null=True, blank=True, ) acquired_by = M2M(to=Profile, related_name='knowledge_packets', blank=True) skills = M2M(to=Skill, related_name='knowledge_packets') picture_sets = M2M(to=PictureSet, related_name='knowledge_packets', blank=True) def informables(self): qs = Profile.active_players.all() qs = qs.exclude(id__in=self.acquired_by.all()) return qs
class PictureSet(Model): objects = PictureSetManager() title = CharField(max_length=200) pictures = M2M(to=Picture, related_name='picture_sets', blank=True) class Meta: ordering = ['title'] def __str__(self): return self.title
class AudioSet(Model): title = CharField(max_length=200) description = TextField(max_length=500, blank=True, null=True) main_audio = FK(to=Audio, on_delete=PROTECT) audios = M2M(to=Audio, related_name='audio_sets', blank=True) class Meta: ordering = ['title'] def __str__(self): return self.title
class FamilyName(Model): form = CharField(max_length=250, unique=True) info = TextField(blank=True, null=True) locations = M2M(to=Location, related_name="family_names", blank=True) group = FK(to=FamilyNameGroup, on_delete=PROTECT, blank=True, null=True) class Meta: ordering = ['group', 'form'] def __str__(self): return self.form
class BiographyPacket(InfoPacket): """A class for per-Persona info that may be visible to Players.""" author = FK(to=Profile, related_name='authored_bio_packets', on_delete=PROTECT, null=True, blank=True) acquired_by = M2M(to=Profile, related_name='biography_packets', blank=True) picture_sets = M2M(to=PictureSet, related_name='biography_packets', blank=True) order_no = SmallIntegerField(default=1) class Meta: ordering = ['order_no', 'sorting_name'] def informables(self): qs = Profile.active_players.all() qs = qs.exclude(id__in=self.acquired_by.all()) qs = qs.exclude(id=self.author_id) return qs
class Perk(Model): """A class describing a special ability of an item or a skill level.""" name = CharField(max_length=50, unique=True, blank=True, null=True) description = TextField(max_length=4000, blank=True, null=True) modifiers = M2M(to=Modifier, related_name='perks', blank=True) cost = TextField(max_length=1000, blank=True, null=True) def __str__(self): return self.name class Meta: ordering = ['name', 'description']
class Level(Model): profile_klass = FK(to=ProfileKlass, related_name='levels', on_delete=CASCADE) achievements = M2M(to=Achievement, related_name='levels', blank=True) level_number = PositiveSmallIntegerField() level_mods = TextField() def __str__(self): return f'{self.profile_klass} [{self.level_number}]' class Meta: ordering = ['profile_klass', 'level_number']
class Klass(Model): name = CharField(max_length=100, unique=True) profession = FK(to=Profession, related_name='klasses', on_delete=PROTECT) description = TextField(max_length=4000, blank=True, null=True) start_perks = TextField(max_length=4000, blank=True, null=True) lvl_1 = CharField(max_length=500, blank=True, null=True) lvl_2 = CharField(max_length=500, blank=True, null=True) lvl_3 = CharField(max_length=500, blank=True, null=True) lvl_4 = CharField(max_length=500, blank=True, null=True) lvl_5 = CharField(max_length=500, blank=True, null=True) lvl_6 = CharField(max_length=500, blank=True, null=True) lvl_7 = CharField(max_length=500, blank=True, null=True) lvl_8 = CharField(max_length=500, blank=True, null=True) lvl_9 = CharField(max_length=500, blank=True, null=True) lvl_10 = CharField(max_length=500, blank=True, null=True) lvl_11 = CharField(max_length=500, blank=True, null=True) lvl_12 = CharField(max_length=500, blank=True, null=True) lvl_13 = CharField(max_length=500, blank=True, null=True) lvl_14 = CharField(max_length=500, blank=True, null=True) lvl_15 = CharField(max_length=500, blank=True, null=True) lvl_16 = CharField(max_length=500, blank=True, null=True) lvl_17 = CharField(max_length=500, blank=True, null=True) lvl_18 = CharField(max_length=500, blank=True, null=True) lvl_19 = CharField(max_length=500, blank=True, null=True) lvl_20 = CharField(max_length=500, blank=True, null=True) allowed_profiles = M2M( to=Profile, limit_choices_to=Q(status='player'), related_name='allowed_klasses', blank=True, ) sorting_name = CharField(max_length=250, blank=True, null=True) def __str__(self): return self.name def save(self, *args, **kwargs): if self.name: self.sorting_name = create_sorting_name(self.name) super().save(*args, **kwargs) def short_name(self): return rid_of_special_chars(self.name) class Meta: ordering = ['sorting_name'] verbose_name = 'Klass' verbose_name_plural = 'Klasses'
class SkillType(Model): """A classification category for Skills.""" kinds = M2M(to=SkillKind, related_name='skill_types', blank=True) name = CharField(max_length=100, unique=True) sorting_name = CharField(max_length=101, blank=True, null=True) def save(self, *args, **kwargs): if self.name: self.sorting_name = create_sorting_name(self.name) super().save(*args, **kwargs) def __str__(self): kinds = "|".join([str(kind) for kind in self.kinds.all()]) return f"[{kinds}] {self.name}" class Meta: ordering = ['sorting_name']
class ItemStorage(Model): name = CharField(max_length=250) description = TextField() owners = M2M(to=Profile, related_name='item_storages') location = FK( to=SecondaryLocation, related_name='item_storages', on_delete=PROTECT, blank=True, null=True, ) def __str__(self): return f'{self.name}: {", ".join([o for o in self.owners.all()])}' class Meta: ordering = ['name']
class TimeUnit(Model): objects = TimeUnitManager() # Fields for all proxies date_start = FK( to=Date, related_name='timeunits_started', on_delete=PROTECT, verbose_name='Date start (year of the encompassing unit)', # TODO - blank and null are only for recreating events - delete later blank=True, null=True, ) date_end = FK( to=Date, related_name='timeunits_ended', on_delete=PROTECT, verbose_name='Date end (year of the encompassing unit)', blank=True, null=True, ) in_timeunit = FK( to='self', related_name='timeunits', on_delete=PROTECT, blank=True, null=True, ) date_in_period = CharField(max_length=99, blank=True, null=True) date_in_era = CharField(max_length=99, blank=True, null=True) date_in_chronology = CharField(max_length=99, blank=True, null=True) description_short = TextField(blank=True, null=True) description_long = TextField(blank=True, null=True) # Fields for TimeSpan & HistoryEvent proxies known_short_desc = M2M( to=Profile, related_name='timeunits_known_short_desc', # limit_choices_to=PLAYERS, limit_choices_to=Q(status='player'), blank=True, ) known_long_desc = M2M( to=Profile, related_name='timeunits_long_desc', # limit_choices_to=PLAYERS, limit_choices_to=Q(status='player'), blank=True, ) # Fields for TimeSpan proxy name = CharField(max_length=256, blank=True, null=True) name_genetive = CharField(max_length=256, blank=True, null=True) # Fields for HistoryEvent & GameEvent proxies plot_threads = M2M(to=PlotThread, related_name='events', blank=True) locations = M2M(to=Location, related_name='events', blank=True) audio = FK( to=Audio, related_name='events', on_delete=PROTECT, blank=True, null=True, ) # GameEvent proxy game = FK( to=GameSession, related_name='game_events', on_delete=PROTECT, blank=True, null=True, ) event_no_in_game = PositiveSmallIntegerField( validators=[MinValueValidator(1)], blank=True, null=True, ) known_directly = M2M( to=Profile, related_name='events_known_directly', # limit_choices_to=PLAYERS, limit_choices_to=Q(status='player'), blank=True, ) known_indirectly = M2M( to=Profile, related_name='events_known_indirectly', # limit_choices_to=PLAYERS, limit_choices_to=Q(status='player'), blank=True, ) picture_sets = M2M(to=PictureSet, related_name='events', blank=True) # debates = M2M(to=Debate, related_name='events', blank=True) debates = M2M(to=Thread, related_name='events', blank=True) class Meta: ordering = ['date_start'] verbose_name_plural = '* Time Units (Time spans, History events, Game events)' def __str__(self): res = str(self.name) if self.in_timeunit and self.date_start: res += f' ({self.date_start.year}-)' if self.date_end: res = res[:-1] + f'{self.date_end.year}' + res[-1] if not self.timeunits.all(): res = res[:-1] + res[-1] res = res[:-1] + f' | {self.in_timeunit.name_genetive}' + res[-1] return res def informables(self): qs = Profile.active_players.all() qs = qs.exclude(id__in=(self.known_directly.all() | self.known_indirectly.all())) return qs
class MapPacket(InfoPacket): acquired_by = M2M(to=Profile, related_name='map_packets', blank=True) picture_sets = M2M(to=PictureSet, related_name='map_packets')