Example #1
0
class PositionCandidacy(models.Model):
    class Meta:
        unique_together = (("election", "candidate"), )

    election = models.ForeignKey(PositionElection)
    candidate = models.ForeignKey('world.Character')
    description = models.TextField()
    retired = models.BooleanField(default=False)
Example #2
0
class PositionElection(models.Model):
    position = models.ForeignKey(Organization)
    turn = models.IntegerField()
    closed = models.BooleanField(default=False)
    winner = models.ForeignKey('PositionCandidacy', blank=True, null=True)

    def open_candidacies(self):
        return self.positioncandidacy_set.filter(retired=False)

    def last_turn_to_present_candidacy(self):
        return self.turn - 3

    def can_present_candidacy(self):
        return self.position.world.current_turn <= \
               self.last_turn_to_present_candidacy()

    @transaction.atomic
    def resolve(self):
        max_votes = 0
        winners = []
        for candidacy in self.open_candidacies().all():
            votes = candidacy.positionelectionvote_set.count()
            if votes > max_votes:
                max_votes = votes
                winners = []
            if votes == max_votes:
                winners.append(candidacy)

        if len(winners) != 1:
            self.position.convoke_elections()
        else:
            winning_candidacy = winners[0]
            winning_candidate = winning_candidacy.candidate
            self.winner = winning_candidacy
            self.position.character_members.remove(
                self.position.get_position_occupier())
            self.position.character_members.add(winning_candidate)

        self.position.last_election = self
        self.position.current_election = None
        self.position.save()
        self.closed = True
        self.save()

    def get_results(self):
        return self.positioncandidacy_set.all().annotate(
            num_votes=Count('positionelectionvote'))\
            .order_by('-num_votes')

    def get_absolute_url(self):
        return reverse('organization:election',
                       kwargs={'election_id': self.id})

    def __str__(self):
        return "{} election for {}".format(
            world.templatetags.extra_filters.nice_turn(self.turn),
            self.position)
Example #3
0
class PolicyDocument(models.Model):
    organization = models.ForeignKey(Organization)
    parent = models.ForeignKey('PolicyDocument',
                               related_name='children',
                               null=True,
                               blank=True)
    public = models.BooleanField(default=False)
    title = models.TextField(max_length=100)
    body = models.TextField()
    last_modified_turn = models.IntegerField()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('organization:document',
                       kwargs={'document_id': self.id})
Example #4
0
class CapabilityProposal(models.Model):
    proposing_character = models.ForeignKey('world.Character')
    capability = models.ForeignKey(Capability)
    proposal_json = models.TextField()
    vote_end_turn = models.IntegerField()
    executed = models.BooleanField(default=False)
    closed = models.BooleanField(default=False)
    democratic = models.BooleanField()

    def announce_proposal(self):
        message = shortcuts.create_message(
            "New {}".format(self),
            self.capability.organization.world,
            category="proposal",
            link=self.get_absolute_url(),
        )
        shortcuts.add_organization_recipient(message,
                                             self.capability.applying_to)
        shortcuts.add_organization_recipient(message,
                                             self.capability.organization)

    def announce_execution(self, text, category, link=None):
        message = shortcuts.create_message(
            ("{}'s proposal passed: {}" if self.democratic else
             "Action by {}: {}").format(self.proposing_character, text),
            self.capability.organization.world,
            category=category,
            link=(self.get_absolute_url()
                  if link is None and self.democratic else link),
        )
        shortcuts.add_organization_recipient(message,
                                             self.capability.applying_to)
        shortcuts.add_organization_recipient(message,
                                             self.capability.organization)
        return message

    def execute(self):
        proposal = self.get_proposal_json_content()
        applying_to = self.capability.applying_to
        if self.capability.type == Capability.POLICY_DOCUMENT:
            try:
                if proposal['new']:
                    document = PolicyDocument(organization=applying_to)
                else:
                    document = PolicyDocument.objects.get(
                        id=proposal['document_id'])

                if proposal['delete']:
                    self.announce_execution(
                        "The document {} has been deleted".format(document),
                        "policy")
                    document.delete()
                else:
                    document.title = proposal.get('title')
                    document.body = proposal.get('body')
                    document.public = proposal.get('public') is not None
                    document.last_modified_turn = self.capability.\
                        organization.world.current_turn
                    document.save()

                    self.announce_execution(
                        ("A new document titled {} has been created"
                         if proposal['new'] else
                         "The document {} has been edited").format(document),
                        "policy",
                        link=document.get_absolute_url())

            except PolicyDocument.DoesNotExist:
                pass

        elif self.capability.type == Capability.BAN:
            try:
                character_to_ban = world.models.Character.objects.get(
                    id=proposal['character_id'])
                applying_to.remove_member(character_to_ban)
            except world.models.Character.DoesNotExist:
                pass

            self.announce_execution(
                "{} has been banned from {}".format(character_to_ban,
                                                    applying_to), 'ban')

        elif self.capability.type == Capability.CONVOKE_ELECTIONS:
            if applying_to.current_election is None:
                months_to_election = proposal['months_to_election']
                election = applying_to.convoke_elections(months_to_election)

        elif self.capability.type == Capability.DIPLOMACY:
            try:
                target_organization = Organization.objects.get(
                    id=proposal['target_organization_id'])
                target_relationship = proposal['target_relationship']
                changing_relationship = applying_to.\
                    get_relationship_to(target_organization)
                reverse_relationship = changing_relationship.reverse_relation()
                action_type = proposal['type']
                if action_type == 'propose':
                    changing_relationship.desire(target_relationship)
                elif action_type == 'accept':
                    if reverse_relationship.desired_relationship == \
                            target_relationship:
                        changing_relationship.set_relationship(
                            target_relationship)
                elif action_type == 'take back':
                    if changing_relationship.desired_relationship == \
                            target_relationship:
                        changing_relationship.desired_relationship = None
                        changing_relationship.save()
                elif action_type == 'refuse':
                    if reverse_relationship.desired_relationship == \
                            target_relationship:
                        reverse_relationship.desired_relationship = None
                        reverse_relationship.save()

            except Organization.DoesNotExist:
                pass

        elif self.capability.type == Capability.MILITARY_STANCE:
            try:
                target_organization = Organization.objects.get(
                    id=proposal['target_organization_id'])
                if 'region_id' in proposal.keys():
                    region = world.models.Tile.objects.get(
                        id=proposal['region_id'])
                    target_stance = applying_to.\
                        get_region_stance_to(target_organization, region)
                else:
                    target_stance = applying_to.\
                        get_default_stance_to(target_organization)
                target_stance.stance_type = proposal.get('target_stance')
                target_stance.save()

                self.announce_execution(
                    ("The general military stance of {from_} towards {to} "
                     "is now {stance}" if target_stance.region is None else
                     "The military stance of {from_} towards {to} "
                     "in {region} is now {stance}").format(
                         from_=target_stance.from_organization,
                         to=target_stance.to_organization,
                         stance=target_stance.get_stance_type_display(),
                         region=target_stance.region,
                     ), 'military stance')

            except (world.models.Tile.DoesNotExist, Organization.DoesNotExist):
                pass

        elif self.capability.type == Capability.BATTLE_FORMATION:
            try:
                formation = BattleFormation.objects.get(
                    organization=applying_to, battle=None)
            except BattleFormation.DoesNotExist:
                formation = BattleFormation(organization=applying_to,
                                            battle=None)
            formation.formation = proposal['formation']
            formation.spacing = proposal['spacing']
            formation.element_size = proposal['element_size']
            formation.save()

            self.announce_execution(
                "The battle formation of {} has been changed ({})".format(
                    applying_to, formation.formation), 'battle formation')

        elif self.capability.type == Capability.CONQUEST:
            try:
                tile = world.models.Tile.objects.get(id=proposal['tile_id'])
                if proposal['stop']:
                    tile_event = world.models.TileEvent.objects.get(
                        tile=tile,
                        organization=applying_to,
                        end_turn__isnull=True)
                    tile_event.end_turn = applying_to.\
                        world.current_turn
                    tile_event.save()
                else:
                    if tile in \
                            applying_to.conquestable_tiles():
                        world.models.TileEvent.objects.create(
                            tile=tile,
                            type=world.models.TileEvent.CONQUEST,
                            organization=applying_to,
                            counter=0,
                            start_turn=applying_to.world.current_turn)
                        tile.world.broadcast(
                            "{} has started conquering {}!".format(
                                applying_to, tile), 'conquest',
                            tile.get_absolute_url())
            except (world.models.Tile.DoesNotExist,
                    world.models.TileEvent.DoesNotExist):
                pass

        elif self.capability.type == Capability.GUILDS:
            try:
                settlement = world.models.Settlement.objects.get(
                    id=proposal['settlement_id'])
                if ((settlement.tile in applying_to.get_all_controlled_tiles())
                        and
                    (proposal['option'] in [
                        choice[0]
                        for choice in world.models.Settlement.GUILDS_CHOICES
                    ])):
                    settlement.guilds_setting = proposal['option']
                    settlement.save()
                    self.announce_execution(
                        "{} in {}".format(
                            settlement.get_guilds_setting_display(),
                            settlement), 'guilds',
                        settlement.tile.get_absolute_url())
            except world.models.Settlement.DoesNotExist:
                pass

        elif self.capability.type == Capability.HEIR:
            try:
                first_heir = world.models.Character.objects.get(
                    id=proposal['first_heir'])
                if (first_heir in applying_to.get_heir_candidates()
                        and first_heir != applying_to.get_position_occupier()):
                    applying_to.heir_first = first_heir
                    applying_to.save()

                    second_heir = None if proposal['second_heir'] == 0 else \
                        world.models.Character.objects.get(
                            id=proposal['second_heir']
                        )
                    if second_heir is None or (
                            second_heir in applying_to.get_heir_candidates()
                            and second_heir !=
                            applying_to.get_position_occupier()):
                        applying_to.heir_second = second_heir
                        applying_to.save()

                    message_content = "{} is now the heir of {}.".format(
                        applying_to.heir_first, applying_to)
                    if applying_to.heir_second:
                        message_content += " {} is the second in the line of" \
                                           "succession".format(
                                                applying_to.heir_second
                                            )
                    message = shortcuts.create_message(
                        message_content,
                        applying_to.world,
                        'heir',
                        link=applying_to.get_absolute_url())
                    shortcuts.add_organization_recipient(
                        message, applying_to.get_violence_monopoly())

            except world.models.Character.DoesNotExist:
                pass

        else:
            raise Exception(
                "Executing unknown capability action_type '{}'".format(
                    self.capability.type))

        self.executed = True
        self.closed = True
        self.save()

    def get_proposal_json_content(self):
        return json.loads(self.proposal_json)

    def issue_vote(self, character, vote):
        CapabilityVote.objects.create(proposal=self,
                                      voter=character,
                                      vote=vote)
        self.execute_if_enough_votes()
        self.close_if_enough_votes()

    def delete_disallowed_votes(self):
        for vote in self.capabilityvote_set.all():
            if not self.capability.organization.character_is_member(
                    vote.voter):
                vote.delete()

    def execute_if_enough_votes(self):
        self.delete_disallowed_votes()
        possible_votes = self.votes_possible()
        if self.votes_yea().count() > possible_votes / 2:
            self.execute()

    def close_if_enough_votes(self):
        self.delete_disallowed_votes()
        possible_votes = self.votes_possible()
        if self.votes_nay().count() > possible_votes / 2:
            self.closed = True
            self.save()

    def votes_possible(self):
        return self.capability.organization.character_members.count()

    def execute_if_majority(self):
        self.delete_disallowed_votes()
        if self.votes_yea().count() > self.votes_nay().count():
            self.execute()

    def votes_yea(self):
        return self.capabilityvote_set.filter(vote=CapabilityVote.YEA)

    def votes_nay(self):
        return self.capabilityvote_set.filter(vote=CapabilityVote.NAY)

    def votes_invalid(self):
        return self.capabilityvote_set.filter(vote=CapabilityVote.INVALID)

    def get_absolute_url(self):
        return reverse('organization:proposal',
                       kwargs={'proposal_id': self.id})

    def __str__(self):
        return "{} proposal by {}".format(self.capability.get_type_display(),
                                          self.proposing_character)
Example #5
0
class Organization(models.Model):
    DEMOCRATIC = 'democratic'  # decisions are voted among members
    DISTRIBUTED = 'distributed'  # decisions can be taken by each member
    DECISION_TAKING_CHOICES = (
        (DEMOCRATIC, DEMOCRATIC),
        (DISTRIBUTED, DISTRIBUTED),
    )

    CHARACTER = 'character'
    ORGANIZATION = 'organization'
    MEMBERSHIP_TYPE_CHOICES = (
        (CHARACTER, CHARACTER),
        (ORGANIZATION, ORGANIZATION),
    )

    INHERITED = 'inherited'
    ELECTED = 'elected'
    POSITION_TYPE_CHOICES = (
        (INHERITED, INHERITED),
        (ELECTED, ELECTED),
    )

    world = models.ForeignKey('world.World')
    name = models.CharField(max_length=100)
    color = models.CharField(max_length=6,
                             default="FFFFFF",
                             help_text="Format: RRGGBB (hex)")
    barbaric = models.BooleanField(default=False)
    description = models.TextField()
    is_position = models.BooleanField()
    position_type = models.CharField(max_length=15,
                                     choices=POSITION_TYPE_CHOICES,
                                     blank=True,
                                     default='')
    owner = models.ForeignKey('Organization',
                              null=True,
                              blank=True,
                              related_name='owned_organizations')
    leader = models.ForeignKey('Organization',
                               null=True,
                               blank=True,
                               related_name='leaded_organizations')
    owner_and_leader_locked = models.BooleanField(
        help_text="If set, this organization will have always the same "
        "leader as it's owner.")
    violence_monopoly = models.BooleanField(default=False)
    decision_taking = models.CharField(max_length=15,
                                       choices=DECISION_TAKING_CHOICES)
    membership_type = models.CharField(max_length=15,
                                       choices=MEMBERSHIP_TYPE_CHOICES)
    character_members = models.ManyToManyField('world.Character', blank=True)
    organization_members = models.ManyToManyField('Organization', blank=True)
    election_period_months = models.IntegerField(default=0)
    current_election = models.ForeignKey('PositionElection',
                                         blank=True,
                                         null=True,
                                         related_name='+')
    last_election = models.ForeignKey('PositionElection',
                                      blank=True,
                                      null=True,
                                      related_name='+')
    heir_first = models.ForeignKey('world.Character',
                                   blank=True,
                                   null=True,
                                   related_name='first_heir_to')
    heir_second = models.ForeignKey('world.Character',
                                    blank=True,
                                    null=True,
                                    related_name='second_heir_to')
    tax_countdown = models.SmallIntegerField(default=0)

    def remove_member(self, member):
        if member not in self.character_members.all():
            raise Exception("{} is not a member of {}".format(member, self))
        self.character_members.remove(member)

        message_content = "{} left {}.".format(member, self)

        if self.leader and member in \
                self.leader.character_members.all():
            self.leader.remove_member(member)

        if member.get_violence_monopoly() is None:
            member.world.get_barbaric_state().character_members.add(member)

        if self.is_position:
            if (self.heir_first
                    and self.heir_first in self.get_heir_candidates()):
                self.character_members.add(self.heir_first)
                self.heir_first = self.heir_second = None
                self.save()
                message_content += " {} is the new {}.".format(
                    self.get_position_occupier(), self)
            elif (self.heir_second
                  and self.heir_second in self.get_heir_candidates()):
                self.character_members.add(self.heir_second)
                self.heir_first = self.heir_second = None
                self.save()
                message_content += " {} is the new {}.".format(
                    self.get_position_occupier(), self)
            elif self.position_type == self.ELECTED:
                self.convoke_elections()

        if self.leader and self.leader.character_is_member(member):
            self.leader.remove_member(member)

        message = shortcuts.create_message(message_content,
                                           self.world,
                                           'leaving',
                                           link=self.get_absolute_url())
        shortcuts.add_organization_recipient(message, self)
        for org in self.leaded_organizations.all():
            shortcuts.add_organization_recipient(message, org)

    def get_descendants_list(self, including_self=False):
        descendants = list()
        if including_self:
            descendants.append(self)
        for child in self.owned_organizations.all():
            descendants += child.get_descendants_list(True)
        return descendants

    def get_membership_including_descendants(self):
        members = set(self.character_members.all())
        for child in self.owned_organizations.all():
            members |= child.get_membership_including_descendants()
        return members

    def character_can_use_capabilities(self, character):
        if character in self.character_members.all():
            return True

    def organizations_character_can_apply_capabilities_to_this_with(
            self, character, capability_type):
        result = []
        capabilities = Capability.objects.filter(applying_to=self,
                                                 type=capability_type)
        for capability in capabilities:
            if capability.organization.character_can_use_capabilities(
                    character):
                result.append(capability.organization)
        return result

    def character_is_member(self, character):
        return character in self.character_members.all()

    def get_violence_monopoly(self):
        if self.violence_monopoly:
            return self
        try:
            return self.leaded_organizations.get(violence_monopoly=True)
        except Organization.DoesNotExist:
            pass
        if self.owner:
            return self.owner.get_violence_monopoly()
        return None

    def conquestable_tiles(self):
        if not self.violence_monopoly:
            return None
        candidate_tiles = world.models.Tile.objects \
            .filter(world=self.world) \
            .exclude(controlled_by=self) \
            .exclude(type__in=(world.models.Tile.SHORE,
                               world.models.Tile.DEEPSEA))
        result = []
        for tile in candidate_tiles:
            conquest_tile_event = tile.tileevent_set.filter(
                organization=self,
                type=world.models.TileEvent.CONQUEST,
                end_turn__isnull=True)
            conquering_units = tile.get_units()\
                .filter(owner_character__in=self.character_members.all())\
                .exclude(status=world.models.WorldUnit.NOT_MOBILIZED)
            if (conquering_units.exists()
                    and not conquest_tile_event.exists()):
                result.append(tile)
        return result

    def get_open_proposals(self):
        return CapabilityProposal.objects.filter(capability__organization=self,
                                                 closed=False)

    def get_all_controlled_tiles(self):
        return world.models.Tile.objects.filter(
            controlled_by__in=self.get_descendants_list(including_self=True))

    def external_capabilities_to_this(self):
        return self.capabilities_to_this.exclude(organization=self)

    def get_position_occupier(self):
        if not self.is_position or not self.character_members.exists():
            return None
        return list(self.character_members.all())[0]

    def get_relationship_to(self, organization):
        return OrganizationRelationship.objects.get_or_create(
            defaults={
                'relationship':
                (OrganizationRelationship.WAR if organization.barbaric
                 or self.barbaric else OrganizationRelationship.PEACE)
            },
            from_organization=self,
            to_organization=organization)[0]

    def get_relationship_from(self, organization):
        return organization.get_relationship_to(self)

    def get_default_stance_to(self, state):
        return MilitaryStance.objects.get_or_create(from_organization=self,
                                                    to_organization=state,
                                                    region=None)[0]

    def get_region_stances_to(self, state):
        return MilitaryStance.objects.filter(
            from_organization=self,
            to_organization=state,
        ).exclude(region=None)

    def get_region_stance_to(self, state, region):
        return MilitaryStance.objects.get_or_create(from_organization=self,
                                                    to_organization=state,
                                                    region=region)[0]

    def get_default_formation_settings(self):
        try:
            return BattleFormation.objects.get(organization=self, battle=None)
        except BattleFormation.DoesNotExist:
            return BattleFormation.objects.create(
                organization=self,
                battle=None,
                formation=BattleFormation.LINE,
                element_size=2,
                spacing=2,
            )

    @transaction.atomic
    def convoke_elections(self, months_to_election=6):
        if not self.is_position:
            raise Exception("Elections only work for positions")
        election = PositionElection.objects.create(
            position=self, turn=self.world.current_turn + months_to_election)
        self.current_election = election
        self.save()
        if self.get_position_occupier() is not None:
            PositionCandidacy.objects.create(
                election=election,
                candidate=self.get_position_occupier(),
                description="Auto-generated candidacy for incumbent character."
            )

        message = shortcuts.create_message(
            "Elections have been convoked for the position {}. "
            "They will take place in {} months.".format(
                self, months_to_election),
            self.world,
            'elections',
            link=election.get_absolute_url())
        shortcuts.add_organization_recipient(message, self)
        for org in self.leaded_organizations.all():
            shortcuts.add_organization_recipient(message, org)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('organization:view',
                       kwargs={'organization_id': self.id})

    def get_html_name(self):
        template = '{name}{icon}{suffix}'
        icon = self.get_bootstrap_icon()

        occupier = self.get_position_occupier()
        if occupier:
            suffix = '<small>{}</small>'.format(occupier.name)
        else:
            suffix = ''

        return template.format(name=escape(self.name),
                               icon=icon,
                               suffix=suffix)

    def get_bootstrap_icon(self):
        template = '<span style="color: #{color}" ' \
                   'class="glyphicon glyphicon-{icon}" ' \
                   'aria-hidden="true"></span>'
        if self.violence_monopoly and not self.barbaric:
            icon = "tower"
        elif self.violence_monopoly and self.barbaric:
            icon = "fire"
        elif self.leaded_organizations.filter(violence_monopoly=True).exists():
            icon = "king"
        elif self.get_violence_monopoly():
            icon = "knight"
        elif self.leaded_organizations.exists():
            icon = "menu-up"
        elif not self.owner:
            icon = "triangle-top"
        else:
            icon = "option-vertical"
        return template.format(
            icon=icon,
            color=escape(self.color),
        )

    def get_html_link(self):
        return '<a href="{}">{}</a>'.format(self.get_absolute_url(),
                                            self.get_html_name())

    def current_elections_can_vote_in(self):
        result = []
        elect_capabilities = Capability.objects.filter(type=Capability.ELECT,
                                                       organization=self)
        for capability in elect_capabilities:
            if capability.applying_to.current_election is not None:
                result.append(capability)
        return result

    def get_heir_candidates(self):
        # TODO this works only for violence monopolies
        return self.get_violence_monopoly().\
            get_membership_including_descendants()