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 from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema 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') character = api.namespace('character', description='Character operations') encounter = api.namespace('encounter', description='Encounter operations') ability_model = ability.model('Ability', ability_model) ability_schema = AbilitySchema() hp_model = hp.model('HP', hp_model) # TODO: Add hitpoint schema 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 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): def get(self): return roll_dices(1, 20, False), 200 @api.route('/roll/chance', methods=['GET']) class RollChance(Resource): def get(self): return roll_dices(1, 100, False), 200 @api.route('/coinflip', methods=['GET']) class RollCoinflip(Resource): def get(self): return random.choice(['Heads', 'Tails']), 200 @api.route('/character/generate') 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 = {} 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 = [] physical_mutations_scores = [] for _ in range(mental_mutations_cnt): mscore = roll_dices(1, 100, False).get('result') + intscore if mscore in mental_mutations_scores: mscore = roll_dices(1, 100, False).get('result') + intscore if mscore > 100: mscore = 100 mental_mutations_scores.append(mscore) for _ in range(physical_mutations_cnt): pscore = roll_dices(1, 100, False).get('result') + conscore if pscore in physical_mutations_scores: pscore = roll_dices(1, 100, False).get('result') + conscore if pscore > 100: pscore = 100 physical_mutations_scores.append(pscore) mut = Mutations() mmut = [] pmut = [] for score in mental_mutations_scores: mutation = mut.get_mental_mutation(score) mmut.append(mutation) mutations_table['mental'] = mmut for score in physical_mutations_scores: mutation = mut.get_physical_mutation(score) pmut.append(mutation) mutations_table['physical'] = pmut return mutations_table 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()