431 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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 = hp_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()
 |