from flask import Flask from flask_cors import CORS from flask_restx import Api, Resource from models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model, mutation_model from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema, HPSchema, \ MutationSchema from encounters import EncounterTable from mentattack import MentalAttackMatrix from mutations import Mutations from cybermods import CyberMods import random app = Flask(__name__) CORS(app) app.config.SWAGGER_UI_JSONEDITOR = True app.config['SWAGGER_UI_JSONEDITOR'] = True api = Api(app, version='1.0', title='Gamma World Dice', description='Rolled Dice As A Service') dice = api.namespace('dice', description='Dice operations') ability = api.namespace('ability', description='Ability operations') hp = api.namespace('hp', description='HP operations') ma = api.namespace('ma', description='Mental Attack operations') mut = api.namespace('mut', description='Mutation operations') character = api.namespace('character', description='Character operations') encounter = api.namespace('encounter', description='Encounter operations') ability_model = ability.model('Ability', ability_model) ability_schema = AbilitySchema() mutation_model = mut.model('Mutation', mutation_model) mutation_schema = MutationSchema() hp_model = hp.model('HP', hp_model) hp_schema = HPSchema() dice_model = dice.model('Dice', dice_model) dice_schema = DiceSchema() ma_model = ma.model('MA', ma_model) ma_schema = MentalAttackSchema() character_model = character.model('Character', character_model) character_schema = CharacterSchema() encounter_model = encounter.model('Encounter', encounter_model) encounter_schema = EncounterSchema() @api.route('/roll/dice', methods=['POST']) class RollDice(Resource): @dice.expect(dice_model) def post(self): data = api.payload errors = dice_schema.validate(data) if errors: return errors, 400 quantity = data.get('quantity') geometry = data.get('geometry') discard_lowest = data.get('discard_lowest') if quantity is None or geometry is None: return {"message": "Required dice data not provided"}, 400 return roll_dices(quantity, geometry, discard_lowest), 200 @api.route('/roll/ability', methods=['POST']) class RollAbility(Resource): @ability.expect(ability_model) def post(self): data = api.payload errors = ability_schema.validate(data) if errors: return errors, 400 chartype = data.get('chartype') attribute = data.get('ability') return roll_ability_scores(chartype, attribute), 200 @api.route('/roll/hp', methods=['POST']) class RollHP(Resource): @hp.expect(hp_model) def post(self): data = api.payload errors = ability_schema.validate(data) if errors: return errors, 400 chartype = data.get('chartype') conscore = data.get('conscore') if conscore is None: return {"message": "A constitution score is required"}, 400 if chartype == 'human': geometry = 8 else: geometry = 6 return roll_dices(conscore, geometry, False), 200 @api.route('/roll/encounter', methods=['POST']) class RollEncounter(Resource): @encounter.expect(encounter_model) def post(self): data = api.payload errors = encounter_schema.validate(data) if errors: return errors, 400 terrain = data.get('terrain').lower() roll = roll_dices(1, 20, False).get('result') et = EncounterTable() creature = et.get_encounter(roll, terrain) if creature is None: creature = "All clear!" return {"encounter": creature}, 200 @api.route('/roll/attack/mental', methods=['POST']) class RollMentalAttack(Resource): @ma.expect(ma_model) def post(self): data = api.payload errors = ma_schema.validate(data) if errors: return errors, 400 ams = data.get('ams') dms = data.get('dms') modifier = data.get('modifier') result = {} mam = MentalAttackMatrix() needed = mam.get_attack_score(ams, dms) result["needed"] = needed outcome = None if needed < 2: outcome = "Automatic Success!" elif needed > 20: outcome = "Absolutely No Chance!" else: raw_roll = roll_dices(1, 20, False).get('result') if modifier != 0: result["original-roll"] = raw_roll result["modifier"] = modifier rolled = raw_roll + modifier # negative modifiers will subtract themselves naturally result["adjusted-roll"] = rolled else: rolled = raw_roll result["original-roll"] = rolled if (rolled >= 20) and (needed <= 16): outcome = "Devastating Success!" elif needed <= rolled: outcome = "Attack Successful!" elif needed > rolled: outcome = "Attack Failed!" result["outcome"] = outcome return result, 200 @api.route('/roll/tohit', methods=['GET']) class RollToHit(Resource): @staticmethod def get(): return roll_dices(1, 20, False), 200 @api.route('/roll/chance', methods=['GET']) class RollChance(Resource): @staticmethod def get(): return roll_dices(1, 100, False), 200 @api.route('/coinflip', methods=['GET']) class RollCoinflip(Resource): @staticmethod def get(): return random.choice(['Heads', 'Tails']), 200 @api.route('/roll/mutations', methods=['POST']) class RollMutations(Resource): @mut.expect(mutation_model) def post(self): data = api.payload errors = mutation_schema.validate(data) if errors: return errors, 400 conscore = data.get('conscore') intscore = data.get('intscore') return roll_mutations(conscore, intscore), 200 @api.route('/character/generate', methods=['POST']) class GenerateCharacter(Resource): @character.expect(character_model) def post(self): data = api.payload errors = character_schema.validate(data) if errors: return errors, 400 chartype = data.get('chartype') character_sheet = {} if chartype == 'mutant': character_sheet['animal-stock'] = (roll_mutant_animal()) ability_scores = roll_ability_scores(chartype) character_sheet['abilities'] = ability_scores character_sheet['hp'] = roll_hp(chartype, ability_scores['constitution']) character_sheet['gold'] = roll_dices(4, 4, False).get('result') * 10 character_sheet['domar'] = roll_dices(2, 4, False).get('result') * 5 if chartype == 'humanoid' or chartype == 'mutant': character_sheet['mutations'] = ( roll_mutations(ability_scores['constitution'], ability_scores['intelligence']) ) if chartype == 'cyborg': character_sheet['cybermods'] = ( roll_cybermods() ) return character_sheet, 200 def roll_dices(dice_count, dice_sides, discard_lowest): roll_results = [] discarded = [] for _ in range(dice_count): roll_results.append(random.randint(1, dice_sides)) if discard_lowest and len(roll_results) > 0: discarded.append(min(roll_results)) roll_results.remove(min(roll_results)) if discarded: net_dice_count = dice_count - 1 mnemonic = str(dice_count) + "(-1)d" + str(dice_sides) else: mnemonic = str(dice_count) + 'd' + str(dice_sides) net_dice_count = dice_count result = { 'dice-set': { 'mnemonic': mnemonic, 'min-roll': net_dice_count, 'max-roll': dice_sides * net_dice_count }, 'rolls': roll_results, 'result': sum(roll_results) } if discarded: result['discarded'] = discarded return result def roll_ability_scores(chartype, attribute=None): if attribute is None or attribute == "all": ability_list = ['p-strength', 'm-strength', 'constitution', 'dexterity', 'charisma', 'intelligence'] else: ability_list = [attribute] ability_scores = {} for abil in ability_list: if ((chartype == 'human') and ((abil == 'intelligence') or (abil == 'charisma') or (abil == 'constitution'))): roll = roll_dices(4, 6, False) # humans are special if (abil == 'constitution') and roll["result"] > 18: roll["capped"] = True roll["result"] = 18 if (abil == 'intelligence' or abil == 'charisma') and roll["result"] > 21: roll["capped"] = True roll["result"] = 21 ability_scores[abil] = roll["result"] else: roll = roll_dices(4, 6, True) # nothing else is special ability_scores[abil] = roll["result"] return ability_scores def roll_hp(chartype, conscore): if chartype == 'human': geometry = 8 else: geometry = 6 return roll_dices(conscore, geometry, False).get('result') def roll_cybermods(): """ Cybermods are similar to mutations, except that they are artificial changes to the body, where mutations are 'natural' changes to the body. Fewer cybermods are possible than with mutations. So, you make one roll of 1d4, and then parse up the score between the two types of mods, as you see fit. If no emphasis is specified, the score will be split as evenly as possible. If the score is 1, then a physical mod will be the default. :return: table of cybermods (in json format) """ cybermods_table = {} cybermods_count = roll_dices(1, 4, False)["result"] phys_cnt = split_number(cybermods_count)[1] # This ends up being the higher of the two numbers ment_cnt = split_number(cybermods_count)[0] cybermods_table['count'] = {'mental': ment_cnt, 'physical': phys_cnt} mental_cybermods_scores = [] physical_cybermods_scores = [] for _ in range(ment_cnt): mscore = roll_dices(1, 10, False).get('result') if mscore in mental_cybermods_scores: mscore = roll_dices(1, 10, False).get('result') mental_cybermods_scores.append(mscore) for _ in range(phys_cnt): pscore = roll_dices(1, 10, False).get('result') if pscore in physical_cybermods_scores: pscore = roll_dices(1, 10, False).get('result') physical_cybermods_scores.append(pscore) cyb = CyberMods() mcyb = [] pcyb = [] for score in mental_cybermods_scores: modification = cyb.get_mental_cybermod(score) mcyb.append(modification) if len(mcyb) != 0: cybermods_table['mental'] = mcyb for score in physical_cybermods_scores: modification = cyb.get_physical_cybermod(score) pcyb.append(modification) if len(pcyb) != 0: cybermods_table['physical'] = pcyb return cybermods_table def roll_mutations(conscore, intscore): """ :param conscore: modifier for physical mutation determination :param intscore: modifier for mental mutation determination :return: table of mutations (in json form) """ mutations_table = {} mental_mutations_cnt = roll_dices(1, 4, False).get('result') physical_mutations_cnt = roll_dices(1, 4, False).get('result') mutations_table['count'] = {'mental': mental_mutations_cnt, 'physical': physical_mutations_cnt} mental_mutations_scores = get_score_list(mental_mutations_cnt, intscore) mutations_table['mental'] = get_mutations(mental_mutations_scores) physical_mutations_scores = get_score_list(physical_mutations_cnt, conscore) mutations_table['physical'] = get_mutations(physical_mutations_scores) return mutations_table def get_mutations(scorelist): mut_table = Mutations() mut_list = [] for score in scorelist: mutation = mut_table.get_mental_mutation(score) mut_list.append(mutation) return mut_list def get_score_list(count, modifier): tmp_scores = [] for _ in range(count): roll = roll_dices(1, 100, False).get('result') + modifier if roll in tmp_scores: roll = roll_dices(1, 100, False).get('result') + modifier if roll > 100: roll = 100 tmp_scores.append(roll) return tmp_scores def roll_mutant_animal(): creature = dict() animal_stock = ['badger', 'racoon', 'hound', 'wolf', 'big-cat', 'fox', 'spider', 'lizard', 'ant', 'hornet', 'hawk', 'ostrich', 'emu', 'crocodile', 'snake', 'rabbit', 'rat', 'bear', 'elephant', 'platypus', 'bull', 'horse', 'goat', 'bat', 'silverfish', 'cockroach', 'turtle', 'gibbon', 'penguin', 'orangutan', 'chimpanzee', 'housefly', 'lobster', 'crab', 'prawn', 'pig', 'chicken', 'duck', 'parrot', 'mouse', 'heron', 'weasel', 'squirrel', 'pigeon', 'crow', 'house-cat', 'shark', 'dolphin', 'narwhal', 'buffalo'] posture = ['upright', 'prone', 'mixed'] creature['stock'] = random.choice(animal_stock) creature['posture'] = random.choice(posture) if creature['posture'] == 'upright' or creature['posture'] == 'mixed': creature['arms'] = random.choice(list(range(2, 4, 2))) # if you're upright 4 is the limit creature['legs'] = random.choice(list(range(2, 6, 2))) # same with legs if creature['posture'] == 'prone': creature['legs'] = random.choice(list(range(4, 8, 2))) # if you're prone, you only get legs return creature def split_number(n): first_split = n // 2 # this will split the number in two equal parts, if it is even second_split = n % 2 + first_split # this will add one to one part if the number is odd return first_split, second_split if __name__ == '__main__': app.run()