view gamelib/missions.py @ 124:685301e35f88

Add a minion cost to missions
author Neil Muller <drnlmuller@gmail.com>
date Thu, 10 May 2012 12:16:15 +0200
parents 7cd716328a44
children be79e113d494
line wrap: on
line source

# -*- coding: utf-8 -*-
# vim:fileencoding=utf-8 ai ts=4 sts=4 et sw=4

from random import randint

from gamelib.constants import SUCCESS, FAILURE, GAME_WIN, NEW_MILESTONE, M_VALS
from gamelib.schematics import cat


class Result(object):
    """Results of a mission"""

    def __init__(self, outcome, money, reputation, msg):
        self.outcome = outcome
        self.money = money
        self.reputation = reputation
        self.message = msg
        self.applied = False

    def apply(self, state):
        if not self.applied:
            state.money += self.money
            state.reputation += self.reputation
            self.applied = True
        else:
            raise RuntimeError('attempted to apply result twice')


class Mission(object):
    """Base class for the mission objects.
       Missions have a name, short description (for list displays) and
       long description (which may contain clues about approaches)"""

    NAME = "Generic Mission"
    SHORT_DESCRIPTION = None
    LONG_DESCRIPTION = None

    MINIMUM_REPUTATION = None
    MINIMUM_MILESTONE = "neighbourhood"
    MINIONS_REQUIRED = 1

    def __init__(self, init_data=None):
        self.data = {}

        if init_data is not None:
            # Load stored state.
            self._load_data(init_data)
        else:
            # New instance.
            self._new_data()

    def _new_data(self):
        pass

    def _load_data(self, init_data):
        # Note: this does not deep-copy.
        self.data = init_data.copy()

    def save_data(self):
        # Note: this does not deep-copy.
        return self.data.copy()

    def get_data(self, key):
        return self.data.get(key, None)

    @classmethod
    def sanity_check(cls):
        pass

    def can_attempt(self, state):
        """Can we currently attempt the mission"""
        if (M_VALS[self.MINIMUM_MILESTONE] > M_VALS[state.milestone]):
            # Our base of operations is too small
            return False
        if self.get_data('completed'):
            return False
        if (self.MINIMUM_REPUTATION is not None and
            self.MINIMUM_REPUTATION > state.reputation):
            # Don't have the reputation required
            return False
        if self.MINIONS_REQUIRED > state.minions:
            # Need more minions!
            return False
        return True

    def attempt(self, equipment, state):
        """Attempt the mission with the given equipment list.
        Returns a result object with the results of the mission."""

        # Handle error case
        if self.get_data('completed'):
            raise RuntimeError('Cannot attempt a completed mission')

        # No equipment is usually a special case.
        if not equipment:
            return self.attempt_no_equipment(state)

        # Categorise equipment for easier decision-making.
        categorised = {}
        for item in equipment:
            for category in item.CATEGORIES:
                categorised.setdefault(category, []).append(item)
        result = self.attempt_with(categorised, state)

        if result is not None:
            # We have a mission result.
            return result

        # Generic fallback result.
        return Result(FAILURE, 0, 0, (
                "You fail to inspire sufficient fear."))

    def attempt_no_equipment(self, state):
        return Result(FAILURE, 0, 0, (
                "Really? You're going in completely unequipped?"
                " How brave. And foolish. And ultimately unsuccessful."))

    def attempt_with(self, categorised, state):
        return Result(FAILURE, 0, 0, "You can't succceed at this mission.")


class PlaygroundBully(Mission):

    NAME = "Rob kids in the playground"
    SHORT_DESCRIPTION = "Steal from those significantly weaker than yourself."
    LONG_DESCRIPTION = (
        "It's not menancing, or lucrative, but when the bills are due, no one"
        " cares how you earn the money.")

    MINIMUM_MILESTONE = "basement"

    def attempt(self, equipment, state):
        haul = randint(90, 110)
        return Result(SUCCESS, haul, -1, (
                "You devote your resources to robbing kids in a playpark."
                " It's not the finest moment in your reign of terror, but at"
                " least you walked away with a surprising amount of small"
                " change."))


class RansomChina(Mission):

    NAME = "Hold China to ransom"
    SHORT_DESCRIPTION = "Surely a path to riches and fame."
    LONG_DESCRIPTION = (
        "Holding China to ransom. The rewards for successfully threatening"
        " the largest country in the world are great, but the risks are"
        " significant. Without some serious firepower, the chances of success"
        " are small.")

    MINIMUM_REPUTATION = 100
    MINIMUM_MILESTONE = "city"

    def _new_data(self):
        self.data['prior_attempts'] = []

    def attempt_no_equipment(self, state):
        return Result(FAILURE, 0, -10, (
                "It takes three different interpreters before the Chinese"
                " government finally understand that you're trying to hold"
                " them ransom with... well... nothing. Nothing at all. This"
                " one will probably make a good anecdote at diplomatic"
                " cocktail parties. But not for you. No, not for you at all."))

    def attempt_with(self, categorised, state):
        dooms = categorised.get(cat.DOOMSDAY_DEVICE, [])

        if not dooms:
            return Result(FAILURE, 0, -1, (
                    "You completely fail to inspire the requisite level of"
                    " terror. Maybe a more impressive threat will fare"
                    " better."))

        if len(dooms) > 1:
            return Result(FAILURE, 0, 0, (
                    "Everyone seems confused as to how you actually plan"
                    " to cause widepsread distruction and mayhem, and"
                    " negotiations break down. Perhaps it's better to stick"
                    " to one weapon of mass destruction at a time."))

        [doom] = dooms
        if doom.NAME in self.data['prior_attempts']:
            return Result(FAILURE, 0, 0, (
                    "'We have devised countermeasures since last time, doctor."
                    " You cannot threaten us with that again.'"))

        self.data['prior_attempts'].add(doom.NAME)
        return Result(SUCCESS, 1000000, 10, (
                "Trembling at you threat of certain doom, the Chinese"
                " government pays the ransom."))


class ToppleThirdWorldGovernment(Mission):

    NAME = "Topple a third-world government"
    SHORT_DESCRIPTION = "We could use a more amenable dictator there."
    LONG_DESCRIPTION = (
        "It's a small and fairly useless country, but it's still an actual"
        " government that can be toppled. A good test bed for some of the"
        " larger toys in the armory.")

    MINIMUM_REPUTATION = 50

    def attempt_no_equipment(self, state):
        return Result(FAILURE, 0, 0, (
                "The border post may be poorly guarded, but you need to"
                " bring *some* kind of weaponry along. Your troops sulk"
                " on the way home."))

    def attempt_with(self, categorised, state):
        if any(c in categorised for c in (cat.VEHICLE, cat.HAND_WEAPON)):
            return Result(SUCCESS, randint(5000, 15000), randint(3, 7), (
                    "The corruption and oppression continue, but at least"
                    " the proceeds are making their way into *your*"
                    " pockets. And you don't even have to dirty your own"
                    " jackboots."))

        if cat.DOOMSDAY_DEVICE in categorised:
            return Result(FAILURE, 0, 0, (
                    "Nobody seems to quite understand what it is you're"
                    " threatening them with. Eventually you have to give up"
                    " and go home."))


class RobBank(Mission):

    NAME = "Rob the local bank"
    SHORT_DESCRIPTION = "A trivial challenge, but easy money."
    LONG_DESCRIPTION = (
        "The security guards and local police are of minor concern. Walk in,"
        " clean out the vault, walk out. Couldn't be simpler.")

    MINIMUM_MILESTONE = "basement"

    def attempt_no_equipment(self, state):
        return Result(FAILURE, 0, 0, (
                "Your attempt to rob the bank barehanded is unsuccessful."
                " Fortunately, everyone is too stunned to impede your"
                " escape."))

    def attempt_with(self, categorised, state):
        loot = randint(500, 1500)

        if cat.VEHICLE in categorised:
            return Result(FAILURE, 0, 0, (
                    "Your vehicles are impressively doom-laden, but not really"
                    " suitable for city traffic. You intimidate the traffic"
                    " wardens into letting you off without a fine, but by the"
                    " time you get to the bank it's already closed."))

        if cat.HAND_WEAPON in categorised:
            return Result(SUCCESS, loot, 0, (
                    "The threat of your weapons is enough to inspire an"
                    " impressive level of cooperation. You make off with the"
                    " loot."))

        if cat.PATHOGEN in categorised:
            if state.reputation < 10:
                return Result(FAILURE, 0, 0, (
                        "The clerk doesn't realise the threat of"
                        " the vial you hold, and, although watching him"
                        " die in agony would be statisfying, you decide"
                        " it's not worth wasting this on something so"
                        " trivial"))
            else:
                return Result(SUCCESS, loot, 1, (
                        "Holding up a bank with only a small vial of clear"
                        " liquid. Now that is power."))


class DestroyMountRushmore(Mission):

    NAME = "Destroy Mount Rushmore"
    SHORT_DESCRIPTION = "Monuments to other people? Intolerable"
    LONG_DESCRIPTION = (
            "While potentially expensive, destroying a major monument is a"
            " good way to secure your reputation.")
    MINIMUM_REPUTATION = 20

    def attempt(self, equipment, state):
        self.data['completed'] = True
        return Result(SUCCESS, 0, 50, (
                "Mount Rushmore is remarkably easy to destroy."))


class DistributePamphlets(Mission):

    NAME = "Distribute pamphlets"
    SHORT_DESCRIPTION = "The populace need to be told the truth!"
    LONG_DESCRIPTION = (
        "A focused pamphlet distribution campaign will combat the lies being"
        " spread about you. Replacing them with better lies, of course.")

    MINIMUM_MILESTONE = "basement"

    SUCCESS_MESSAGE = (
        "A small army of urchins delivers thousands of cheaply printed"
        " pamphlets. %s")

    def attempt_no_equipment(self, state):
        rep = randint(-2, 5)

        if rep < 0:
            result = (
                "Sadly, the populace was so annoyed by the flood of flyers"
                " that nobody took any notice of the content.")
        elif rep == 0:
            result = "Nobody seems to have noticed."
        else:
            result = "The public seemed mildly receptive to your propaganda."

        return Result(SUCCESS, 0, rep, self.SUCCESS_MESSAGE % (result,))

    def attempt_with(self, categorised, state):
        rep = randint(5, 10)

        if cat.MIND_CONTROL in categorised:
            result = (
                "Your creative use of science has paid off nicely.")
            return Result(SUCCESS, 0, rep, self.SUCCESS_MESSAGE % (result,))
        else:
            return self.attempt_no_equipment(state)


class TakeOverTheWorld(Mission):

    NAME = "Take over the world!"
    SHORT_DESCRIPTION = "It's for their own good."
    LONG_DESCRIPTION = (
        "Someone has to rule the world and if it's not you it'd just be"
        " someone less well qualified -- and that would be worse for"
        " everyone.")

    MINIMUM_REPUTATION = 200
    MINIMUM_MILESTONE = "city"

    def attempt_no_equipment(self, state):
        return Result(FAILURE, 0, 0, "It's going to take more than your bare"
                      " hands to take over the world!")

    def attempt_with(self, categorised, state):
        if cat.MIND_CONTROL not in categorised:
            return Result(FAILURE, 0, 5, "If you're going to take over the"
                          " world, first you must control key elements within"
                          " the populace.")
        return Result(GAME_WIN, 0, 100, "The world is yours!")


class TakeOverTheNeighbourhood(Mission):

    NAME = "Take over the neighbourhood!"
    SHORT_DESCRIPTION = "First step toward greatness."
    LONG_DESCRIPTION = (
        "A basement lab is a good starting point, but it's getting a bit"
        " cramped in here. You need to expand, but don't quite have the"
        " resources to take the whole world yet. Or even the city. But the"
        " neighbourhood... that's quite feasible.")

    MINIMUM_REPUTATION = 20
    MINIMUM_MILESTONE = "basement"

    def attempt_no_equipment(self, state):
        return Result(FAILURE, 0, 0, "The neighbourhood isn't very big, but"
                      " you're still not going to take it over bare-handed.")

    def attempt_with(self, categorised, state):
        if all(c in categorised for c in (cat.MIND_CONTROL, cat.HAND_WEAPON)):
            self.game['completed'] = True
            return Result(NEW_MILESTONE, 1000, 5, "Guns and persuasion, that's"
                          " all you need. It's early days still, but you're"
                          " finally out of that pokey basement and have some"
                          " elbow room to work with. Next step: the city!")
        if cat.HAND_WEAPON in categorised:
            return Result(SUCCESS, 150, 0, "You'll need more than guns to"
                          " win the people over. But until then, you can take"
                          " their cash.")
        if cat.MIND_CONTROL in categorised:
            return Result(SUCCESS, 0, 7, "Propaganda and persuasion are good"
                          " tools, but you'll need something to back them up.")
        return Result(FAILURE, 0, 0, "Well, that didn't work. Maybe a better"
                      " equipment selection is in order.")