view gamelib/missions.py @ 209:5ca97ed09738

rat mission
author Neil Muller <drnlmuller@gmail.com>
date Sat, 12 May 2012 19:52:29 +0200
parents ed25c335fd67
children 53e78cddb9a4
line wrap: on
line source

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

from random import randint, random, choice

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


class Result(Exception):
    """Results of a mission.

    This is an exception so we can throw it from inside helper methods.
    """

    def __init__(self, outcome, msg, money=0, rep=0, **special):
        self.outcome = outcome
        self.money = money
        self.reputation = rep
        self.text = msg
        self.special = special
        self.applied = False

    @property
    def loot(self):
        loot = {}
        if self.money != 0:
            loot['money'] = self.money
        if self.reputation != 0:
            loot['rep'] = self.reputation
        loot.update(self.special)
        loot.pop('new_milestone', None)  # This one's special.
        return loot

    def apply(self, state):
        if not self.applied:
            state.money += self.money
            state.reputation += self.reputation
            state.apply_mission_special(**self.special)
            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

    GENERIC_FAILURE = "You fail to inspire sufficient fear."
    GENERIC_SUCCESS = "Victory is sweet."
    NO_EQUIP_FAILURE = (
        "Really? You're going in completely unequipped? How brave. And "
        " foolish. And ultimately unsuccessful.")

    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)

    def get_description(self):
        return self.LONG_DESCRIPTION

    @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 fail(self, msg=None, money=0, rep=0, **special):
        if msg is None:
            msg = self.GENERIC_FAILURE
        raise Result(FAILURE, msg, money=money, rep=rep, **special)

    def succeed(self, msg=None, money=0, rep=0, **special):
        if msg is None:
            msg = self.GENERIC_SUCCESS
        raise Result(SUCCESS, msg, money=money, rep=rep, **special)

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

    def attempt_mission(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')

        try:
            self.attempt(equipment, state)
        except Result, e:
            return e

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

        # Try with some equipment.
        self.attempt_with(self.categorise_equipment(equipment), state)

        # No result, so generic failure.
        self.fail()

    def attempt_no_equipment(self, state):
        self.fail(self.NO_EQUIP_FAILURE)

    def attempt_with(self, categorised, state):
        self.fail("You can't succceed at this mission.")

    def combat_power(self, categorised, cats=None):
        if cats is None:
            cats = [cat.HAND_WEAPON, cat.VEHICLE, cat.COUNTERMEASURE,
                    cat.INTELLIGENCE]

        combat_equipment = set()
        for category in cats:
            combat_equipment.update(categorised.get(category, []))

        power = 0
        for equipment in combat_equipment:
            power += equipment.power()
        return power

    def check_failure(self, categorised):
        equipment = set()
        for items in categorised.values():
            equipment.update(items)
        for item in equipment:
            if item.reliability() < random():
                return item.FAILURE_TEXT
        return None

    def use_equipment(self, categorised, msg="Disaster strikes! %s", **loot):
        failure = self.check_failure(categorised)
        if failure is not None:
            self.fail(msg % (failure,), **loot)

    def use_equipment_category(self, categorised, category):
        self.use_equipment({category: categorised[category]})


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"
    GENERIC_SUCCESS = (
        "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.")

    def attempt(self, equipment, state):
        self.succeed(money=randint(90, 110), rep=-1)


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):
        self.fail(
            "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.",
            rep=-10)

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

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

        if len(dooms) > 1:
            self.fail(
                "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']:
            self.fail(
                "'We have devised countermeasures since last time, doctor."
                " You cannot threaten us with that again.'")

        self.use_equipment(categorised, rep=randint(-5, -2))

        self.data['prior_attempts'].add(doom.NAME)
        self.succeed(
            "Trembling at you threat of certain doom, the Chinese"
            " government pays the ransom.",
            money=randint(800000, 1200000), rep=10)


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

    GENERIC_FAILURE = (
        "Your invasion force was outclassed by the incumbent military, Not"
        " surprising, since it turns out they've had a lot of practice in the"
        " recent series of revolutions.")

    NO_EQUIP_FAILURE = (
        "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):
        self.use_equipment(categorised)
        if self.combat_power(categorised) > randint(60, 120):
            self.succeed(
                "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.",
                money=randint(5000, 15000), rep=randint(3, 7))


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"

    GENERIC_FAILURE = (
        "The bank's security arrangements are rather more impressive than you"
        " were led to believe. Bring bigger guns next time.")
    NO_EQUIP_FAILURE = (
        "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)

        self.use_equipment(categorised)

        if cat.VEHICLE in categorised:
            self.fail(
                "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.PATHOGEN in categorised:
            if randint(5, 15) < state.reputation:
                self.fail(
                    "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.")
            self.succeed(
                "Holding up a bank with only a small vial of clear liquid. Now"
                " that is power.",
                money=loot, rep=1)

        if cat.AI in categorised:
            self.success(
                "Your cunning AI easily penetrates the bank's computing"
                " systems and transfers the money to you account.",
                money=loot)

        if self.combat_power(categorised) > randint(5, 20):
            self.succeed(
                "The threat of your weapons is enough to inspire an impressive"
                " level of cooperation. You make off with the loot.",
                money=loot)


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
        self.succeed("Mount Rushmore is remarkably easy to destroy.", rep=50)


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 _succ(self, message, rep):
        self.succeed(self.SUCCESS_MESSAGE % (message,), rep=rep)

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

        if rep < 0:
            self._succ(
                "Sadly, the populace was so annoyed by the flood of flyers"
                " that nobody took any notice of the content.", rep)
        if rep == 0:
            self._succ("Nobody seems to have noticed.", rep)

        self._succ(
            "The public seemed mildly receptive to your propaganda.", rep)

    def attempt_with(self, categorised, state):
        if cat.MIND_CONTROL in categorised:
            self.use_equipment_category(categorised, cat.MIND_CONTROL)
            self._succ("Your creative use of science has paid off nicely.",
                       randint(5, 10))

        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"

    NO_EQUIP_FAILURE = (
        "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:
            self.fail(
                "If you're going to take over the world, first you must"
                " control key elements within the populace.", rep=-5)
        raise Result(GAME_WIN, "The world is yours!", rep=100)


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"

    NO_EQUIP_FAILURE = (
        "The neighbourhood isn't very big, but you're still not going to take"
        " it over bare-handed.")
    GENERIC_FAILURE = (
        "Well, that didn't work. Maybe a better equipment selection is in"
        " order.")

    def attempt_with(self, categorised, state):
        if all(c in categorised for c in (cat.MIND_CONTROL, cat.HAND_WEAPON)):
            self.data['completed'] = True
            self.succeed(
                "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!",
                money=randint(1000, 2000), rep=randint(5, 15),
                new_milestone="neighbourhood", income=100)

        if cat.HAND_WEAPON in categorised:
            self.succeed(
                "You'll need more than guns to win the people over. But until"
                " then, you can take their cash.", money=randint(100, 200))

        if cat.MIND_CONTROL in categorised:
            self.succeed(
                "Propaganda and persuasion are good tools, but you'll need"
                " some force to back them up.", rep=randint(2, 4))


class TakeOverTheCity(Mission):
    NAME = "Take over the city"
    SHORT_DESCRIPTION = "Time to grow"
    LONG_DESCRIPTION = (
        "It's time to spread your wings and take your rightful place as"
        " a major player. And what better way than by securing your base"
        " of operations?")

    MINIMUM_REPUTATION = 100
    MINIMUM_MILESTONE = "neighbourhood"

    NO_EQUIP_FAILURE = (
        "You are thrown out of city hall for making a scene. The fools will"
        " pay for this when you return with more firepower.")
    GENERIC_FAILURE = (
        "Well, that didn't work. Maybe a better equipment selection is in"
        " order.")

    def attempt_with(self, categorised, state):
        if cat.DOOMSDAY_DEVICE in categorised:
            self.data['completed'] = True
            self.succeed(
                "Overwhelming force! It works for governments, and it works"
                " for you. The city cowers before you, and you finally"
                " feel like you're getting somewhere. Soon, the world will"
                " be yours.", money=randint(10000, 20000),
                rep=randint(10, 20), new_milestone='city', income=1000)

        if cat.AI in categorised:
            self.use_equipment_category(categorised, cat.AI)
            self.succeed(
                "While the AI can't control the entire city, you can at"
                " least ensure the budget is well spent",
                money=randint(10000, 30000))

        if cat.MIND_CONTROL in categorised:
            self.use_equipment_category(categorised, cat.MIND_CONTROL)
            self.succeed(
                "Running a city requires controlling more than just a few"
                " key people. Still, making the mayor dance to your tune"
                " is not without it's benefits", money=randint(3000, 7000),
                rep=randint(3, 7))

        if cat.VEHICLE in categorised:
            self.fail(
                "While you can muster a significant force, you don't want"
                " to destroy the city in the inevitable fighting. You need"
                " to ensure no-one tries to resist.")

        if cat.HAND_WEAPON in categorised:
            self.fail(
                "You'll need more than a handful of guns, no matter"
                " how powerful, to subdue a city")


class ShowThemAll(Mission):

    NAME = "And they called me mad!"
    SHORT_DESCRIPTION = "Time for revenge."
    LONG_DESCRIPTION = (
        "You've outgrown the chains of convention your teachers tried"
        " to force you into. It's time to show the world what true"
        " genius can accomplish. And what better way than with a small"
        " slice of revenge?")

    MINIMUM_REPUTATION = 20
    MINIMUM_MILESTONE = "neighbourhood"

    NO_EQUIP_FAILURE = (
        "A security guard escorts you off campus after finding you clawing"
        " at the science building with your bare hands. He doesn't say"
        " anything, but you can already hear the gossip.")

    def attempt_no_equipment(self, state):
        self.fail(self.NO_EQUIP_FAILURE, rep=-5)

    def attempt_with(self, categorised, state):

        self.use_equipment(categorised)

        if cat.DOOMSDAY_DEVICE in categorised:
            self.data['completed'] = True
            self.succeed(
                "The fools cower in terror at your display of power. Finally"
                " they are forced to acknowledge your genius.",
                rep=randint(10, 15))

        if cat.BEAST in categorised:
            if all(cat.AQUATIC in x.CATEGORIES for x in
                    categorised[cat.BEAST]):
                self.fail(
                    "While the beast is terrifying, the effect is"
                    " unfortuantely significantly lessened the need for a "
                    " large water tank, and the it's inability to move "
                    " independantly. Perhaps you need to rethink your plan?")
            else:
                self.data['completed'] = True
                self.succeed(
                    "Your monstrous creation rampages through campus in a"
                    " most statisfying way. They will not forgot this.",
                    rep=randint(5, 12))

        if cat.HAND_WEAPON in categorised:
            self.fail(
                "Mere crude force is not the answer. You need some more"
                " fitting demonstration of your power.")


class SubvertNews(Mission):
    NAME = "Subvert the news network"
    SHORT_DESCRIPTION = "Stop the lies!"
    LONG_DESCRIPTION = (
        "Worringly, people appear to be easily duped by the lies broadcast"
        " about you. Time to fight fire with fire!")

    MINIMUM_REPUTATION = 15
    MINIMUM_MILESTONE = "neighbourhood"

    NO_EQUIP_FAILURE = (
        "You fail to even get into the building. Perhaps you need to"
        " rethink your approach")

    def attempt_with(self, categorised, state):

        self.use_equipment(categorised)

        if cat.MIND_CONTROL in categorised:
            self.succeed(
                "With the proper equipment, it's a simple matter to"
                " convince people of the correctness of your point"
                " of view. If only everything were so simple",
                rep=randint(5, 15))

        if cat.AI in categorised:
            self.succeed(
                "Your AI has complete control of the broadcasters computer"
                " system. After all, it's not censorship if you're stopping"
                " them broadcasting blatant lies, now is it?",
                rep=randint(5, 10))

        if any(c in categorised for c in (cat.VEHICLE, cat.DOOMSDAY_DEVICE,
            cat.BEAST)):
            self.succeed(
                "Cowering in fear, the broadcaster agrees to change the"
                " tone of their stories.", rep=randint(2, 5))

        if cat.HAND_WEAPON in categorised:
            self.fail(
                "The news station's security is surprisingly well prepared."
                " Perhaps you should rethink your approach?",
                rep=-randint(5, 10))


class RaidLab(Mission):
    NAME = "Raid Rival Lab"
    SHORT_DESCRIPTION = "Why not let other people work for you?"
    LONG_DESCRIPTION = (
        "While clearly no match for your genius, sometimes your rivals stumble"
        " onto something interesting. Surely their results can be put to"
        " better use helping your plans?")

    MINIMUM_MILESTONE = "neighbourhood"

    NO_EQUIP_FAILURE = (
        "Your rival may not be the sharpest tool in the shed, but even a"
        " fool invests in some defenses. You'll need to be better prepared"
        " in the future.")

    def _succ(self, msg, state):
        reward = choice(('schematic', 'science',
            'money', 'money', 'money', 'money', 'money',
            'nothing', 'nothing', 'nothing'))
        if reward == 'nothing':
            self.succeed("%s Unfortunately, not only are these people working"
                " on ideas you've already covered, they're flat broke." % msg,
                money=0, rep=1)
        elif reward == 'money':
            self.succeed("%s While their research yields nothing of interest,"
                " you can repurpose their funding to more worthy causes."
                % msg, money=randint(1000, 2000), rep=1)
        elif reward == 'schematic':
            self.succeed("%s You find the plans for a new device. How did"
                 " these fools stumble upon this?" % msg,
                 money=0, rep=randint(2, 5),
                 new_schematic=choice(state.lab.new_schematics))
        # New science
        self.succeed("%s Their notes are most illuminating. You realise you"
            " have sadly neglected research into this field." % msg,
            money=0, rep=randint(2, 5),
            new_science=choice(state.lab.new_research))

    def attempt_with(self, categorised, state):

        self.use_equipment(categorised)

        if cat.DOOMSDAY_DEVICE in categorised:
            self.fail(
                "While overwhelming force is always a tempting choice,"
                " total destruction of the lab will not help you cause.")

        if cat.AI in categorised:
            self._succ("Your AI easily takes control of the lab's network.",
                state)

        if self.combat_power(categorised) > randint(20, 40):
            self._succ(
                "The resistance is stiff, but your forces prevail"
                " thanks to your superior technology.", state)
        else:
            self.fail(
                "The lab is surprisingly well defended for an operation"
                " run by a total fool. You vow to return with a better"
                " prepared force.")


class TerroriseCity(Mission):
    NAME = "Invade nearby city"
    SHORT_DESCRIPTION = "Need more resources"
    LONG_DESCRIPTION = (
        "Sometimes it's nessecary to remind people of what you can do."
        " And why not do so close to home?")

    MINIMUM_MILESTONE = "city"

    NO_EQUIP_FAILURE = (
        "Attacking a city with your bare hands. Perhaps not the best"
        " thought out scheme in the world. Fortunately, only your"
        " pride was hurt by this failure.")
    GENERIC_FAILURE = (
        "You fail to accomplish your goal. A different approach seems"
        " called for.")

    def attempt_no_equipment(self, state):
        self.fail(self.NO_EQUIP_FAILURE, rep=-randint(10, 15))

    def attempt_with(self, categorised, state):

        self.use_equipment(categorised)

        if cat.DOOMSDAY_DEVICE in categorised:
            self.succeed(
                "Destroying the city is a simple exercies with the right"
                " tools. People will tremble at the mention of your name",
                rep=randint(15, 25))

        if cat.AI in categorised:
            self.succeed("Your AI easily takes control of the central"
                 " network. It's not that scary, but it is profitable",
                 money=randint(3000, 10000))

        if cat.BEAST in categorised:
            if any(cat.AQUATIC in x.CATEGORIES for x in
                    categorised[cat.BEAST]):
                rampage = 'harbour'
            else:
                rampage = 'streets'
            self.succeed(
                "Your creature's rampage through the city's %s terrifies"
                " the city's inhabitants. The city council are eager to"
                " pay you to rein it in. A most statisfying outcome" % rampage,
                money=randint(2000, 5000), rep=randint(5, 15))

        if cat.VEHICLE in categorised:
            if self.combat_power(categorised) > randint(50, 70):
                self.succeed("Your overwhelming display of force cowers"
                    " the city. Fear of you spreads.", rep=randint(10, 20))
            else:
                self.fail(
                    "Your forces prove unable to overcome the city's"
                    " defenses. This is an annoying setback, but you"
                    " vow to return.", rep=-randint(3, 7))

        if cat.HAND_WEAPONS in categorised:
            self.fail("A sprinkling of small arms can't overcome the city's"
                " defenses. Something more impressive is required.")


class SecureLair(Mission):
    NAME = "Secure island base"
    SHORT_DESCRIPTION = "A nice safe base of operations"
    LONG_DESCRIPTION = (
        "It's a cliche, but there's a lot to be said for an isolated"
        " and secure base of operations. You can still control your"
        " city from afar. You just have to deal with the local inhabitants"
        " of your chosen location, but they should not present a problem")
    MINIONS_REQUIRED = 4
    MINIMUM_MILESTONE = "city"
    MINIMUM_REPUTATION = 50

    NO_EQUIP_FAILURE = (
        "While the local defenses are weak, even they can deal with"
        " a few unarmed men. Something more suitable is required")
    GENERIC_FAILURE = (
        "Your chosen tools aren't up to the task of securing the"
        " island. Perhaps you need to rethink your approach")

    def attempt_no_equipment(self, state):
        self.fail(self.NO_EQUIP_FAILURE, rep=-randint(10, 15))

    def attempt_with(self, categorised, state):

        self.use_equipment(categorised)

        if cat.DOOMSDAY_DEVICE in categorised:
            self.fail(
                "You want to use the island afterward, and the local"
                " population can provide basic labour. Something less"
                " destructive is required.")

        if cat.AI in categorised:
            self.fail(
                "Your AI complains about the limited bandwidth and"
                " inadequate computing resources of the target, then"
                " goes off to sulk. A more physical approach may"
                " be required.")

        msg = (
            " Afraid of the repurcusions, the people quickly build a base"
            " to your specifications")
        if (cat.BEAST in categorised and
                self.combat_power(categorised) > randint(20, 30)):
            self.data['completed'] = True
            self.succeed(
                "Your creature's terrifying rampage destroys all"
                " resistance, and you quickly conquer the island. %s" % msg,
                rep=randint(10, 15))

        if cat.VEHICLE in categorised:
            if self.combat_power(categorised) > randint(20, 30):
                self.data['completed'] = True
                self.succeed("Your overwhelming display of force cowers"
                    " the local population. The island is yours. %s" % msg,
                    rep=randint(10, 15))
            else:
                self.fail(
                    "Your forces prove unable to overcome the local"
                    " defenses.")


class EliminateRival(Mission):
    NAME = "Eliminate Dr. X."
    SHORT_DESCRIPTION = "A rival. Inconcievable"
    LONG_DESCRIPTION = None  # We handle this one specially
    MINIONS_REQUIRED = 3
    MINIMUM_MILESTONE = "city"
    MINIMUM_REPUTATION = 50

    NO_EQUIP_FAILURE = (
        "While you'd like to tear Dr. X apart with your bare hands, you"
        " do need to deal with his pathetic defenses first.")
    GENERIC_FAILURE = (
        "Somehow, Dr. X has thwarted your attack. You are quite surprised.")

    def _new_data(self):
        # How many times has Dr. X been beaten?
        self.data['times'] = 0
        self.data['turn'] = 0
        self.data['seen'] = False

    def get_description(self):
        if self.get_data('times') == 0:
            return (
                "Recently, Dr. X has made some serious claims abut his"
                " capabilities. While it's laughable to believe he can"
                " actually back up his boasts, such statements cannot go"
                " unpunished.")
        elif self.get_data('times') == 1:
            return (
                "Dr. X has returned. Hasn't he learnt from your previous"
                " encounter? This cannot go unpunished.")
        return (
            "Dr. X has once again appeared on the scene. You suspect"
            " he's mixed his DNA with that of a cockroach, but, "
            " regardless of the cause, this cannot be ignored.")

    def can_attempt(self, state):
        if self.get_data('completed'):
            # does Dr. X return?
            if (state.turn > self.get_data('turn') + 2 and
                    randint(0, 5) < (state.turn - self.get_data('turn'))):
                self.data['completed'] = False
                return True
            else:
                return False
        elif self.get_data('seen'):
            # Once he's appeared, he doesn't go unless completed
            return True
        if super(EliminateRival, self).can_attempt(state):
            self.data['seen'] = True  # flag as seen
            return True
        return False

    def attempt_no_equipment(self, state):
        self.fail(self.NO_EQUIP_FAILURE, rep=-randint(10, 15))

    def attempt_with(self, categorised, state):

        self.use_equipment(categorised)

        if cat.DOOMSDAY_DEVICE in categorised:
            self.data['completed'] = True
            self.data['times'] += 1
            self.data['turn'] = state.turn
            self.succeed(
                "You crush Dr. X with your superior technology. He will"
                " not forgot this lesson anytime soon.",
                rep=randint(10, 20))

        if cat.AI in categorised:
            self.fail(
                "Subverting Dr. X's network is simple enough for your AI,"
                " but you are annoyed to discover that the doctor places"
                " little faith in computers, and there is nothing you can"
                " do to foil his plans this way")

        if (cat.BEAST in categorised and
                self.combat_power(categorised) > randint(40, 60)):
            self.data['completed'] = True
            self.data['times'] += 1
            self.data['turn'] = state.turn
            self.succeed(
                "Your creature destroys Dr. X's lab. He will not quickly"
                " recover this setback", rep=randint(10, 20))

        if cat.VEHICLE in categorised:
            if self.combat_power(categorised) > randint(40, 60):
                self.data['completed'] = True
                self.data['times'] += 1
                self.data['turn'] = state.turn
                self.succeed(
                    "Your forces easily overcome the doctor's defenses,"
                    " but Dr. X himself escapes. You are sure, however,"
                    " he will present no further threat to your plans.",
                    rep=randint(10, 20))
            else:
                self.succeed("Dr. X's defenses are better than you"
                    " anticipated. You'll have to return with more"
                    " firepower.", rep=-randint(3, 7))


class Publish(Mission):
    NAME = "Publish your results"
    SHORT_DESCRIPTION = "The world must know of your genius"
    LONG_DESCRIPTION = (
        "You've seen further than others. Surely you can open their"
        " eyes to the truth (and secure your place in history)?")
    MINIMUM_REPUTATION = 5
    MINIMUM_MILESTONE = "basement"

    NO_EQUIP_FAILURE = (
        "The review board rejects your paper, clearly failing to"
        " realise the significant of your results")
    GENERIC_FAILURE = (
        "You fail to persuade the review board of the need to publish"
        " your results. Perhaps a different appraoch is required?")

    def attempt_with(self, categorised, state):
        if cat.MIND_CONTROL in categorised:
            self.use_equipment_category(categorised, cat.MIND_CONTROL)
            self.data['completed'] = True
            self.succeed("With the correct persuasion, the review board"
                    " recognises the significance of your work.",
                    rep=randint(10, 15))

        if cat.HAND_WEAPON in categorised:
            self.use_equipment_category(categorised, cat.HAND_WEAPON)
            self.data['completed'] = True
            self.succeed("The review board respond favourably to a little"
                    " light intidimation, and your paper is accepted.",
                    rep=randint(10, 15))


class DisruptMarkets(Mission):
    NAME = "Disrupt local enconomy"
    SHORT_DESCRIPTION = "Creating chaos and fear."
    LONG_DESCRIPTION = (
        "People place far too much value on their money. This is a weakness"
        " that just begs to be exploited. And, in the chaos and fear, "
        " there's almost certainly and opportunity to profit.")
    MINIMUM_MILESTONE = "neighbourhood"
    MINIMUM_MONEY = 2000

    NO_EQUIP_FAILURE = (
        "You cannot beat the game just with your mind, doctor. You'll need"
        " to have some extra toys")
    GENERIC_FAILURE = (
        "No-one notices your efforts. Back to the drawing board")

    def can_attempt(self, state):
        if state.money < self.MINIMUM_MONEY:
            # You need to be in the game to cheat at it
            return False
        return super(DisruptMarkets, self).can_attempt(state)

    def attempt_with(self, categorised, state):

        if cat.DOOMSDAY_DEVICE in categorised:
            return self.fail(
                "While destroying the area is certainly within your"
                " capabilities, and would cause wide-spread panic,"
                " there seems little prospect of exploiting the"
                " confusion usefully at this time. You decide to"
                " shelve this plan for now.")

        if cat.WEATHER_MACHINE in categorised:
            self.use_equipment_category(categorised, cat.WEATHER_MACHINE)
            self.succeed(
                "You cunningly use your device to disrupt the nearby farmers."
                " The ensuing panic buyng of supplies is easy to exploit for"
                " profit.", money=randint(5000, 10000), rep=randint(5, 15))

        if cat.AI in categorised:
            self.use_equipment_category(categorised, cat.AI)
            self.succeed(
                "You easily survert the local exchange's computer systems."
                " It hardly seems fair to profit off such a trivial challenge"
                " but there are always bills to pay",
                money=randint(5000, 10000), rep=randint(2, 5))

        if cat.BEAST in categorised:
            self.use_equipment_category(categorised, cat.BEAST)
            self.succeed(
                "Releasing the monster into the exchange certainly created"
                " a stir, and there was much running and screaming, but, with"
                " the exchange closing it's doors until the creature was"
                " contained, there wasn't much opportunity to make a profit.",
                money=0, rep=randint(2, 5))


class ControlMayor(Mission):
    NAME = "Control the Mayor"
    SHORT_DESCRIPTION = "Minions in high places."
    LONG_DESCRIPTION = (
        "While the mayor powers are limited, it's a step towards controlling"
        " the entire city")
    MINIMUM_MILESTONE = "neighbourhood"

    def attempt_with(self, categorised, state):
        if cat.CLONE in categorised:
            self.use_equipment_category(categorised, cat.CLONE)
            self.data['completed'] = True
            self.succeed(
                "Replacing the mayor with a copy under your control. Such"
                " a genius scheme could only come from a brain as brillant"
                " as yours.", rep=randint(10, 15))
        if cat.MIND_CONTROL in categorised:
            self.use_equipment_category(categorised, cat.MIND_CONTROL)
            self.data['completed'] = True
            self.succeed(
                "Mind control. It's hardly a novel approach to the problem"
                " but who can argue with the results.", rep=randint(7, 15))

        if cat.HAND_WEAPONS in categorised:
            self.fail(
                "Brute force. It seems such a crude approach. You decide"
                " this is unworthy of you and abandon the plan.")

        if cat.BEAST in categorised:
            self.fail(
                "In retrospect, unleashing a monster into the downtown streets"
                " was a great way to cause panic, but not a reliable method"
                " for securing the loyalties of the mayor. You return to"
                " the drawing board")


class DisruptCityServices(Mission):
    NAME = "Disrupt city services"
    SHORT_DESCRIPTION = "Keep your name in the news"
    LONG_DESCRIPTION = (
        "You're not being talked about in the news. This cannot be allowed"
        " to continue. Spreading chaos and fear will remind people about"
        " your presence.")
    MINIMUM_MILESTONE = "neighbourhood"
    MAXIMUM_REPUTATION = 10

    NO_EQUIP_FAILURE = (
        "There's little one unarmed man can do to disrupt the city. You'd"
        " better rethink this plan.")

    def can_attempt(self, state):
        if state.reputation > self.MAXIMUM_REPUTATION:
            return False
        return super(DisruptCityServices, self).can_attempt(state)

    def attempt_no_equipment(self, state):
        self.fail(self.NO_EQUIP_FAILURE, rep=-1)

    def attempt_with(self, categorised, state):
        if cat.DOOMSDAY_DEVICE in categorised:
            self.fail(
                "If there's no-one left alive, there'll be no-one to"
                " report on your genius. Prehaps less overkill is more"
                " suited to the task at hand")
        if cat.BEAST in categorised:
            self.use_equipment_catefory(categorised, cat.BEAST)
            if any(cat.AQUATIC in x.CATEGORIES for x in
                    categorised[cat.BEAST]):
                self.succeed(
                    "You release the beast into the city sewer system."
                    " The resulting destruction is both expensive to"
                    " repair and very visible to the inhabitants.",
                    rep=randint(5, 10))
            else:
                self.succeed(
                    "The beast causes considerable property damage, and"
                    " takes out a local substation. It's not a stunning"
                    " victory, but it's enough to get you name in the"
                    " news again", rep=randint(2, 5))

        if cat.VEHICLE in categorised:
            self.use_equipment_catefory(categorised, cat.VEHICLE)
            self.succeed(
                "Your mobile platforms of destruction cause severe"
                " traffic chaos, and tie up the local traffic services"
                " for hours. The resulting traffic jams headline the"
                " evening news. It's not quite the what you were going"
                " for, but it's publicity", rep=randint(0, 3))

        if cat.HAND_WEAPON in categorised:
            self.fail(
                "You want to be known as more than just a local gangster."
                " Guns are an efficient tool, but hardly the means to"
                " securing your reputation.")


class RatArmy(Mission):
    NAME = "Breed Rat Burglars"
    SHORT_DESCRIPTION = "Your furry money source"
    LONG_DESCRIPTION = (
        "Small and easy to breed. An army of rat burgulars will provide"
        " a useful supply of steady income")

    def can_attempt(self, state):
        if self.get_data('completed'):
            return False
        if state.lab.meet_requirements(Biogenetics, 1):
            return True
        return False

    def attempt_no_equipment(self, state):
        self.data['completed'] = True
        self.succeed(
            "You breed an army of small rats, engineered to steal small"
            " change. The resulting income is not much, but still useful",
            money=0, rep=0, income=randint(10, 15))

    def attempt_with(self, categorised, state):
        self.fail(
            "You're overthinking this doctor. Perhaps a simpler approach"
            " will work better?")