"""Test of a gameplay mechanic "I never get to go on any missions. I would be a good mission... person." -Reynold, "Cheat Commandos: Shopping For Danger," homestarrunner.com. This idea is meant for a game idea where you have a base with a fairly large group of characters (say, 100) and you send teams of them out on missions to forage and explore the world. As I originally thought of it, you'd use something like the "Shining Sea" game to play the missions yourself, but it might also be fun to just focus on this aspect of the game. The actual base-building would be more like "Civilization" than an (in my opinion) overly- detailed game like "Theme Park." Here you'd just have stats like "cooking" to represent cooking facilities available. The basic mechanic shown here is, a group of people goes on a foraging mission. They do that by each rolling their Foraging skill, representing a set of dice, against some "difficulty dice." By default their skill stinks, so they usually fail. But then their allies get a chance to make a "teamwork roll," which on a success lets them roll their own skill as a substitute. If _that_ works, it counts as a success for the first character _and_ makes the characters like each other more. Not only can that event be used to provide some color and trigger other events, it means that there's a reason to have characters work together regularly rather than swapping them out on a whim (see Chrono Cross). Dice contests are based on the RPG "Ironclaw": your highest die vs. the opposition's highest. Getting more and bigger dice lets you get a high number more often, but the range of numbers never goes above 20. I was going to do a complicated success formula, but this sounds more fun. """ import random DICE_SIZES = [2,4,8,10,12,20] DICE_POOLS = [[],[2],[4],[8],[10],[12],[12,2],[12,4],[12,8],[12,10],[12,12]] class Person: """Foo""" def __init__(self,**options): self.breed = options.get("breed",RandomBreed(options.get("breed_nature"))) self.sex = options.get("sex",RandomSex()) self.name = options.get("name",RandomName(self.sex)) self.title = options.get("title","") self.age = options.get("age",RandomAge()) self.skills = options.get("skills",{}) ## People I know and (dis?)like. self.relationships = {} ## Friendliness lets me help someone I haven't yet befriended. self.friendliness = options.get("friendliness",0) self.exhausted = False def Describe(self): """Return a quick description of myself.""" text = self.name+", " if self.title: text = self.title+" "+text text += str(self.age/365)+" y.o. "+self.sex+" "+self.breed return text def GetAge(self): return int(self.age / 365) def GetSkill(self,skill): """Return my skill level for something. The format is a dice pool, eg. (4,6) == d4 & d6. Default == [], ie. an automatic 1.""" return self.skills.get(skill,[]) def GetRelationship(self,name): """Return my level of friendship with this person.""" return self.relationships.get(name) class Party: """Foo""" def __init__(self,**options): self.people = [] ## A sample party: Two randoms, a guy who actually has some skill, ## and a character with strong innate friendliness. self.people.append(Person(name="Hiro",skills={"foraging":[4]})) self.people.append(Person(name="Amicus",friendliness=6)) for n in range(options.get("size",2)): self.people.append( Person() ) self.inventory = {} def Describe(self): for person in self.people: print "-"+person.Describe() class Game: """Foo""" def __init__(self,**options): self.party = Party() def Roll(self,dice): """Return the highest score on this set of dice. Result >= 1. Input: a list of die sizes, eg. [6,6,4]""" dice = list(dice) result = 1 for die in dice: roll = random.randint(1,die) if roll > result: result = roll return result def SimpleOpposedRoll(self,first_group,second_group): """Return -1, 0, or 1 based on who wins a dice contest. Each group should be a tuple of die sizes, eg. (6,6,4). 0 == a tie, 1 == first_group wins, -1 == second_group wins. Eg., I roll 3d6 vs. your 1d8, by calling: OpposedRoll([6,6,6],[8]) And I get the higher top score, so this returns 1.""" first_score = self.Roll(first_group) second_score = self.Roll(second_group) return cmp(first_score,second_score) def OpposedRoll(self,first_group,second_group): """Return -2, -1, 0, 1, or 2 based on who wins a dice contest. Each group should be a tuple of die sizes, eg. (6,6,4). 0 == a tie, 1 == first_group wins, -1 == second_group wins. -2 = critical failure (lost by 5+), 2 = critical success (by 5+). Eg., I roll 3d6 vs. your 1d8, by calling: OpposedRoll([6,6,6],[8]) And I get the higher top score, so this returns 1.""" first_score = self.Roll(first_group) second_score = self.Roll(second_group) if first_score - 4 > second_score: return 2 elif first_score > second_score: return 1 elif first_score == second_score: return 0 elif first_score + 4 < second_score: return -2 else: return -1 def ImproveRelationship(self,char1,char2,relationship_pool=[8]): """Possibly form a (stronger) relationship between these two. Relationships' gameplay effect is that they let friendly characters cover for each other on failed skill rolls. The chance of improvement decreases with the strength of the existing relationship. NOTE: Can't yet properly handle relationships at very high level.""" r1, r2 = char1.GetRelationship(char2), char2.GetRelationship(char1) if r1 == None: r1 = [] if r2 == None: r2 = [] r1plus, r2plus = DICE_POOLS[DICE_POOLS.index(r1)+1], DICE_POOLS[DICE_POOLS.index(r2)+1] opposed_pool = r1 + r2 + [4] if self.SimpleOpposedRoll(relationship_pool,opposed_pool) > 0: print "Improved relationship: "+char1.name+" with "+char2.name+"." char1.relationships[char2.name] = r1plus char2.relationships[char1.name] = r2plus def DoForagingTest(self): """Simulate a "mission" where a group of heroes forages. Each hero gets a skill roll. If they fail, there's a chance that someone will help them out. The group's success is noted.""" print "The party goes on a foraging mission in the wilderness." group_success = 0 party = self.party task_difficulty = [4] for n in range(len(party.people)): hero = party.people[n] difficulty = task_difficulty skill = hero.GetSkill("foraging") print "\n"+hero.name+" tries, with skill of "+str(skill)+"..." success = self.OpposedRoll(skill,difficulty) if success == 2: print "Great success!" group_success += 2 elif success >= 0: print "Success!" group_success += 1 elif success < 0: """Now allies get a chance to help. They can try if they win a teamwork roll vs. difficulty d8. By default that'll happen one in 8 times. """ had_enough_help = False for ally in [person for person in party.people if person is not hero]: ## Seussical! if ally.exhausted: continue ally.exhausted = True print " "+ally.name+" made a teamwork roll to try helping." dice_pool = [] dice_pool += [die for die in [hero.friendliness,ally.friendliness] if die > 0] friendship = ally.GetRelationship(hero.name) if friendship: dice_pool += friendship difficulty = [8] print " Dice pool is "+str(dice_pool)+" vs. difficulty "+str(difficulty)+"." ally_helping = self.SimpleOpposedRoll(dice_pool,difficulty) if ally_helping >= 0: print " Success! "+ally.name+" tries to help." ally_skill = ally.GetSkill("foraging") ally_success = self.OpposedRoll(ally_skill,task_difficulty) if ally_success >= 0: print " "+ally.name+" managed to help "+hero.name+" out." group_success += ally_success ## Counts as a success for hero. else: print " But "+ally.name+" wasn't able to help." ## A successful assist improves' characters friendship. self.ImproveRelationship(hero,ally) had_enough_help = True if had_enough_help: break for person in party.people: person.exhausted = False print "\n--- Mission Finished! ---" print "Group's overall success: "+str(group_success) loot = {} for n in range(group_success*2): keys = PLANT_TYPES.keys() item_gotten = keys[random.randint(0,len(PLANT_TYPES)-1)] quantity = random.randint(1,5) loot[item_gotten] = loot.get(item_gotten,0) + quantity print "You got:" for item in loot: print "-"+item+" x"+str(loot[item]) self.party.inventory = loot class GameWorld: """A collection of zones to explore. I was just starting on this; it's not used here.""" def __init__(self,**options): self.zones = [] for y in range(5): for x in range(5): zone = {} self.zones.append(new_zone) ##### Random character creation stuff ##### NAMES_M = ["Abel","Axel","Bann","Bligh","Bonnie","Cid","Cinder","Clyde","Culgan","Deke","Eckert","Finn","Gerhard","Gil","Halleck","Hiro","Hrothgar","Ice","Jak","Kay","Kiln","Knux","Laharl","Lark","Leon","Lex","Maks","Noel","Oliver","Oppenheim","Pershing","Quixote","Rain","Salk","Snow","Stein","Tark","Torn","Venter","Wyrdal","Zel"] NAMES_F = ["Alice","Alys","Amy","Bastet","Belle","Celia","Charmi","Deliah","Diva","Erin","Etna","Flonne","Gina","Hecate","Hestia","Ieanna","Iris","Jinn","Lucca","Katrin","Kas","Kyra","Leia","Ling","Mina","Minx","Nadia","Nayla","Nell","Nephi","Penelope","Pika","Sally","Sara","Silk","Tara","Teeka","Ursula","Vae","Zelda"] NAMES_N = ["Alliam","Ander","Bering","Cubert","Deke","Dex","Eckert","Elias","Fnar","Gerad","Haskell","Illiad","Ivo","Kay","Kirin","Kris","Miles","Oak","Mephit","Morgan","Pip","Qwerty","Rahal","Rem","Seed","Synx","Taran","Ulf","Vector","Vicks","Wein","Xiao","Yi","Zenny","Zera"] BREEDS_HUMAN_RACES = ["aborigine","african","amerindian","castillian","han","hindi","hispanic","nipponese","nordic","saxon"] ## Fanciful. BREEDS_SCIFI = ["human","cy-human","transhuman","parahuman","warmech","workmech","freemech","dolphin uplift","dog uplift","primate uplift","feral uplift"] BREEDS_FANTASY = ["human","elf","half-elf","dwarf","half-dwarf","halfling","drow","orc","half-orc","kobold","goblin","drake"] BREEDS_FURRY = ["antelope","badger","bat","cat","dog","dolphin","ferret","fox","human","otter","rabbit","raccoon","rat","skunk","squirrel","wolf"] ALLBREEDS = {"human":BREEDS_HUMAN_RACES,"scifi":BREEDS_SCIFI,"fantasy":BREEDS_FANTASY,"furry":BREEDS_FURRY} SEXES = {"m":"m","f":"f","n":"n","h":"h","male":"m","female":"f"} SEX_CHANCE = {"m":50,"f":100} def RandomSex(): s = random.randint(0,99) if s < SEX_CHANCE["m"]: return "male" else: return "female" def RandomName(whatkind="n"): if whatkind == "m": l = NAMES_M + NAMES_N elif whatkind == "f": l = NAMES_F + NAMES_N else: l = NAMES_N x = random.randint(0,len(l)-1) return l[x] def RandomBreed(nature=None): category = ALLBREEDS.get(nature) if not category: category = ALLBREEDS[ ALLBREEDS.keys()[random.randint(0,len(ALLBREEDS)-1)] ] n = random.randint(0,len(category)-1) return category[n] def RandomAge(age_range=(18,36)): """A typical adventurer's age.""" return random.randint(365*age_range[0],365*age_range[1]) ## Food / other plant stuff: Creates random stuff to forage for. ## Run it just once if the program's already running in the console. try: PLANT_TYPES except: PLANT_TYPES = {} kinds = ["berry","fruit","root","herb","seed","nut","gourd","veggie"] NUTRITIONS = [-5,-3,-1,0,0,1,1,1,2,2,2,3,3,4,5] COOKING_VALUES = [-1,0,0,1,1,2,2,2,3,3,4] TASTES = ["sweet","salty","bitter","sour","fishy","oily","tangy","tart","spicy"] for plant in kinds: for letter in "ABCD": name = plant.title()+" "+letter value = NUTRITIONS[random.randint(0,len(NUTRITIONS)-1)] cooking_value = COOKING_VALUES[random.randint(0,6)] taste = TASTES[random.randint(0,len(TASTES)-1)] cooking_difficulty = random.randint(1,5) PLANT_TYPES[name] = {"value":value,"cooking value":cooking_value,"cooking difficulty":cooking_difficulty,"taste":taste} ##for p in PLANT_TYPES: ## print p+": "+PLANT_TYPES[p]["taste"]+", "+str(PLANT_TYPES[p]["value"])+" / "+str(PLANT_TYPES[p]["cooking value"]) game = Game() party = game.party party.Describe() game.DoForagingTest()