1
0
Fork 0

Added mission difficulty and reward

This commit is contained in:
Michaël Lemaire 2017-07-11 00:50:38 +02:00
parent 9f170969ba
commit 7c03a1e2d1
10 changed files with 363 additions and 23 deletions

View File

@ -12,11 +12,9 @@ Map/story
* Add initial character creation
* Fix quickly zooming in twice preventing to display some UI parts
* Enemy fleet size should start low and increase with system level
* Enemy fleet size should start low and increase with system level (there should be less locations in systems too)
* Allow to change/buy ship model
* Add ship personality (with icons to identify ?), with reaction dialogs
* Add factions and reputation
* Add generated missions with rewards
* Allow to cancel secondary missions
* Forbid to end up with more than 5 ships in the fleet because of escorts
* Show missions' destination near systems/locations
@ -61,6 +59,7 @@ Ships models and equipments
* Chance to hit should increase with precision
* Add actions with cost dependent of distance (like current move actions)
* Add hull points to drones and make them take area damage
* "Shield Transfer" has no quality offsets
Artificial Intelligence
-----------------------
@ -94,6 +93,7 @@ Postponed
* Replays
* Multiplayer/co-op
* Formation or deployment phase
* Add ship personality (with icons to identify ?), with reaction dialogs
* New battle internal flow: any game state change should be done through revertable events
* Animated arena background, instead of big picture
* Hide enemy information (shield, hull, weapons), until they are in play, or until a "spy" effect is used

View File

@ -108,5 +108,36 @@ module TS.SpaceTac {
ship4.setDead();
expect(fleet.isAlive()).toBe(false);
});
it("adds cargo in first empty slot", function () {
let fleet = new Fleet();
let ship1 = fleet.addShip();
ship1.cargo_space = 1;
let ship2 = fleet.addShip();
ship2.cargo_space = 2;
expect(ship1.cargo).toEqual([]);
expect(ship2.cargo).toEqual([]);
let result = fleet.addCargo(new Equipment());
expect(result).toBe(true);
expect(ship1.cargo).toEqual([new Equipment()]);
expect(ship2.cargo).toEqual([]);
result = fleet.addCargo(new Equipment());
expect(result).toBe(true);
expect(ship1.cargo).toEqual([new Equipment()]);
expect(ship2.cargo).toEqual([new Equipment()]);
result = fleet.addCargo(new Equipment());
expect(result).toBe(true);
expect(ship1.cargo).toEqual([new Equipment()]);
expect(ship2.cargo).toEqual([new Equipment(), new Equipment()]);
result = fleet.addCargo(new Equipment());
expect(result).toBe(false);
expect(ship1.cargo).toEqual([new Equipment()]);
expect(ship2.cargo).toEqual([new Equipment(), new Equipment()]);
});
});
}

View File

@ -102,5 +102,19 @@ module TS.SpaceTac {
return any(this.ships, ship => ship.alive);
}
}
/**
* Add an equipment to the first available cargo slot
*
* Returns true on success, false if no empty cargo slot was available.
*/
addCargo(equipment: Equipment): boolean {
let ship = first(this.ships, ship => ship.getFreeCargoSpace() > 0);
if (ship) {
return ship.addCargo(equipment);
} else {
return false;
}
}
}
}

View File

@ -31,5 +31,41 @@ module TS.SpaceTac.Specs {
expect(result).toBe(false);
expect(mission.current_part).toBe(mission.parts[1]);
})
it("stores a reward", function () {
let mission = new Mission(new Universe());
expect(mission.getRewardText()).toEqual("-");
mission.reward = 720;
expect(mission.getRewardText()).toEqual("720 zotys");
mission.reward = new Equipment();
mission.reward.name = "Super Equipment";
expect(mission.getRewardText()).toEqual("Super Equipment Mk1");
})
it("gives the reward on completion", function () {
let fleet = new Fleet();
let ship = fleet.addShip();
ship.cargo_space = 5;
fleet.credits = 150;
let mission = new Mission(new Universe(), fleet);
mission.reward = 75;
mission.setCompleted();
expect(mission.completed).toBe(true);
expect(fleet.credits).toBe(225);
mission.setCompleted();
expect(fleet.credits).toBe(225);
mission = new Mission(new Universe(), fleet);
mission.reward = new Equipment();
expect(ship.cargo).toEqual([]);
mission.setCompleted();
expect(mission.completed).toBe(true);
expect(fleet.credits).toBe(225);
expect(ship.cargo).toEqual([mission.reward]);
})
})
}

View File

@ -1,4 +1,18 @@
module TS.SpaceTac {
/**
* Reward for a mission (either an equipment or money)
*/
export type MissionReward = Equipment | number
/**
* Level of difficulty for a mission
*/
export enum MissionDifficulty {
easy,
normal,
hard
}
/**
* A mission (or quest) assigned to the player
*/
@ -24,6 +38,13 @@ module TS.SpaceTac {
// Title of this mission (should be kept short)
title: string
// Estimated mission difficulty and value (expected reward value)
difficulty: MissionDifficulty = MissionDifficulty.normal
value = 0
// Reward when this mission is completed
reward: MissionReward | null = null
// Numerical identifier
id = -1
@ -58,6 +79,29 @@ module TS.SpaceTac {
return this.parts.indexOf(this.current_part);
}
/**
* Get a small text describing the associated reward
*/
getRewardText(): string {
if (this.reward) {
if (this.reward instanceof Equipment) {
return this.reward.getFullName();
} else {
return `${this.reward} zotys`;
}
} else {
return "-";
}
}
/**
* Set the difficulty level
*/
setDifficulty(description: MissionDifficulty, value: number) {
this.difficulty = description;
this.value = value;
}
/**
* Set the mission as started (start the first part)
*/
@ -83,7 +127,7 @@ module TS.SpaceTac {
let current_index = this.getIndex();
if (current_index < 0 || current_index >= this.parts.length - 1) {
this.completed = true;
this.setCompleted();
return false;
} else {
this.current_part = this.parts[current_index + 1];
@ -94,5 +138,21 @@ module TS.SpaceTac {
return true;
}
}
/**
* Set the mission as completed, and give the reward to the fleet
*/
setCompleted(): void {
if (!this.completed) {
this.completed = true;
if (this.reward) {
if (this.reward instanceof Equipment) {
this.fleet.addCargo(this.reward);
} else {
this.fleet.credits += this.reward;
}
}
}
}
}
}

View File

@ -1,6 +1,6 @@
module TS.SpaceTac.Specs {
describe("MissionGenerator", () => {
it("generates escort missions", () => {
describe("MissionGenerator", function () {
it("generates escort missions", function () {
let universe = new Universe();
let star1 = universe.addStar(1);
let loc1 = star1.locations[0];
@ -17,11 +17,11 @@ module TS.SpaceTac.Specs {
let escort = <MissionPartEscort>mission.parts[0];
expect(escort.destination).toBe(loc2);
expect(escort.ship.level.get()).toBe(2);
});
})
it("generates location cleaning missions", () => {
it("generates location cleaning missions", function () {
let universe = new Universe();
let star1 = universe.addStar(1);
let star1 = universe.addStar(1, "TTX");
let loc1 = star1.locations[0];
let loc2 = star1.addLocation(StarLocationType.PLANET);
@ -29,10 +29,82 @@ module TS.SpaceTac.Specs {
let mission = generator.generateCleanLocation();
expect(mission.title).toBe("Defeat a level 1 fleet in this system");
expect(mission.parts.length).toBe(1);
expect(mission.parts.length).toBe(2);
expect(mission.parts[0] instanceof MissionPartCleanLocation).toBe(true);
let part = <MissionPartCleanLocation>mission.parts[0];
expect(part.destination).toBe(loc2);
});
let part1 = <MissionPartCleanLocation>mission.parts[0];
expect(part1.destination).toBe(loc2);
expect(part1.title).toEqual("Clean a planet in TTX system");
expect(mission.parts[0] instanceof MissionPartGoTo).toBe(true);
let part2 = <MissionPartGoTo>mission.parts[1];
expect(part2.destination).toBe(loc1);
expect(part2.title).toEqual("Go back to collect your reward");
})
it("helps to evaluate mission difficulty", function () {
let generator = new MissionGenerator(new Universe(), new StarLocation());
let mission = new Mission(generator.universe);
expect(mission.difficulty).toBe(MissionDifficulty.normal);
expect(mission.value).toBe(0);
generator.setDifficulty(mission, 1000, 1);
expect(mission.difficulty).toBe(MissionDifficulty.normal);
expect(mission.value).toBe(1000);
generator.setDifficulty(mission, 1000, 2);
expect(mission.difficulty).toBe(MissionDifficulty.hard);
expect(mission.value).toBe(2200);
generator.setDifficulty(mission, 1000, 3);
expect(mission.difficulty).toBe(MissionDifficulty.hard);
expect(mission.value).toBe(3600);
generator.around.star.level = 10;
generator.setDifficulty(mission, 1000, 10);
expect(mission.difficulty).toBe(MissionDifficulty.normal);
expect(mission.value).toBe(10000);
generator.setDifficulty(mission, 1000, 9);
expect(mission.difficulty).toBe(MissionDifficulty.easy);
expect(mission.value).toBe(8100);
generator.setDifficulty(mission, 1000, 8);
expect(mission.difficulty).toBe(MissionDifficulty.easy);
expect(mission.value).toBe(6400);
})
it("generates equipment reward", function () {
let generator = new MissionGenerator(new Universe(), new StarLocation());
let template = new LootTemplate(SlotType.Weapon, "Test Weapon");
generator.equipment_generator.templates = [template];
template.price = 350;
let result = generator.tryGenerateEquipmentReward(500);
expect(result).toBeNull();
template.price = 800;
result = generator.tryGenerateEquipmentReward(500);
expect(result).toBeNull();
template.price = 500;
result = generator.tryGenerateEquipmentReward(500);
expect(result).not.toBeNull();
})
it("falls back to money reward when no suitable equipment have been generated", function () {
let generator = new MissionGenerator(new Universe(), new StarLocation());
generator.equipment_generator.templates = [];
let result = generator.generateReward(15000);
expect(result).toBe(15000);
let template = new LootTemplate(SlotType.Weapon, "Test Weapon");
template.price = 15000;
generator.equipment_generator.templates.push(template);
generator.random = new SkewedRandomGenerator([0], true);
result = generator.generateReward(15000);
expect(result instanceof Equipment).toBe(true);
})
});
}

View File

@ -21,11 +21,13 @@ module TS.SpaceTac {
universe: Universe
around: StarLocation
random: RandomGenerator
equipment_generator: LootGenerator
constructor(universe: Universe, around: StarLocation, random = RandomGenerator.global) {
this.universe = universe;
this.around = around;
this.random = random;
this.equipment_generator = new LootGenerator(this.random);
}
/**
@ -39,20 +41,77 @@ module TS.SpaceTac {
let generator = this.random.choice(generators);
let result = generator();
// TODO Add reward
if (result.value) {
result.reward = this.generateReward(result.value);
}
return result;
}
/**
* Generate a new ship
* Generate a new ship that may be used in a mission
*/
private generateShip(level: number) {
generateShip(level: number) {
let generator = new ShipGenerator(this.random);
let result = generator.generate(level, null, true);
result.name = `${this.random.choice(POOL_SHIP_NAMES)}-${this.random.randInt(10, 999)}`;
return result;
}
/**
* Try to generate an equipment of given value
*/
tryGenerateEquipmentReward(value: number): Equipment | null {
let minvalue = value * 0.8;
let maxvalue = value * 1.2;
let qualities = [EquipmentQuality.FINE, EquipmentQuality.PREMIUM, EquipmentQuality.LEGENDARY];
let candidates: Equipment[] = [];
for (let pass = 0; pass < 10; pass++) {
let equipment: Equipment | null;
let level = 1;
do {
let quality = qualities[this.random.weighted([15, 12, 2])];
equipment = this.equipment_generator.generate(level, quality);
if (equipment && equipment.getPrice() >= minvalue && equipment.getPrice() <= maxvalue) {
candidates.push(equipment);
}
level += 1;
} while (equipment && equipment.getPrice() < maxvalue * 1.5 && level < 20);
}
if (candidates.length > 0) {
return this.random.choice(candidates);
} else {
return null;
}
}
/**
* Generate a reward
*/
generateReward(value: number): MissionReward {
if (this.random.bool()) {
let equipment = this.tryGenerateEquipmentReward(value);
if (equipment) {
return equipment;
} else {
return value;
}
} else {
return value;
}
}
/**
* Helper to set the difficulty of a mission
*/
setDifficulty(mission: Mission, base_value: number, fight_level: number) {
let level_diff = fight_level - this.around.star.level;
let code = (level_diff > 0) ? MissionDifficulty.hard : (level_diff < 0 ? MissionDifficulty.easy : MissionDifficulty.normal);
let value = fight_level * (base_value + base_value * 0.1 * clamp(level_diff, -5, 5));
mission.setDifficulty(code, Math.round(value));
}
/**
* Generate an escort mission
*/
@ -63,6 +122,7 @@ module TS.SpaceTac {
let ship = this.generateShip(dest_star.level);
mission.addPart(new MissionPartEscort(mission, destination, ship));
mission.title = `Escort a ship to a level ${dest_star.level} system`;
this.setDifficulty(mission, 1000, dest_star.level);
return mission;
}
@ -72,13 +132,16 @@ module TS.SpaceTac {
generateCleanLocation(): Mission {
let mission = new Mission(this.universe);
let dest_star = this.random.choice(this.around.star.getNeighbors().concat([this.around.star]));
let here = (dest_star == this.around.star);
let choices = dest_star.locations;
if (dest_star == this.around.star) {
if (here) {
choices = choices.filter(loc => loc != this.around);
}
let destination = this.random.choice(choices);
mission.addPart(new MissionPartCleanLocation(mission, destination));
mission.title = `Defeat a level ${destination.star.level} fleet in ${(dest_star == this.around.star) ? "this" : "a nearby"} system`;
mission.addPart(new MissionPartGoTo(mission, this.around, "Go back to collect your reward"));
mission.title = `Defeat a level ${destination.star.level} fleet in ${here ? "this" : "a nearby"} system`;
this.setDifficulty(mission, here ? 300 : 500, dest_star.level);
return mission;
}
}

View File

@ -0,0 +1,60 @@
module TS.SpaceTac.UI.Specs {
describe("MissionsDialog", function () {
let testgame = setupEmptyView();
function checkTexts(dialog: MissionsDialog, expected: string[]) {
let i = 0;
let container = <Phaser.Group>(<any>dialog).container;
container.children.forEach(child => {
if (child instanceof Phaser.Text) {
expect(child.text).toEqual(expected[i++]);
}
});
expect(i).toEqual(expected.length);
}
it("displays active and proposed missions", function () {
let universe = new Universe();
let player = new Player();
let shop = new Shop();
let shop_missions: Mission[] = [];
spyOn(shop, "getMissions").and.callFake(() => shop_missions);
let missions = new MissionsDialog(testgame.baseview, shop, player);
checkTexts(missions, []);
let mission = new Mission(universe);
mission.title = "Save the universe!";
mission.setDifficulty(MissionDifficulty.hard, 1);
mission.reward = 15000;
shop_missions.push(mission);
missions.refresh();
checkTexts(missions, ["Proposed jobs", "Save the universe!", "Hard - Reward: 15000 zotys"]);
mission = new Mission(universe);
mission.title = "Do not do evil";
mission.setDifficulty(MissionDifficulty.easy, 1);
mission.reward = new Equipment();
mission.reward.name = "Boy Scout Cap";
shop_missions.push(mission);
missions.refresh();
checkTexts(missions, ["Proposed jobs", "Save the universe!", "Hard - Reward: 15000 zotys", "Do not do evil", "Easy - Reward: Boy Scout Cap Mk1"]);
mission = new Mission(universe);
mission.title = "Collect some money";
mission.setDifficulty(MissionDifficulty.normal, 1);
player.missions.addSecondary(mission, player.fleet);
missions.refresh();
checkTexts(missions, ["Active jobs", "Collect some money", "Normal - Reward: -",
"Proposed jobs", "Save the universe!", "Hard - Reward: 15000 zotys", "Do not do evil", "Easy - Reward: Boy Scout Cap Mk1"]);
mission = new Mission(universe, undefined, true);
mission.title = "Kill the villain";
mission.setDifficulty(MissionDifficulty.hard, 1);
player.missions.main = mission;
missions.refresh();
checkTexts(missions, ["Active jobs", "Collect some money", "Normal - Reward: -",
"Proposed jobs", "Save the universe!", "Hard - Reward: 15000 zotys", "Do not do evil", "Easy - Reward: Boy Scout Cap Mk1"]);
});
});
}

View File

@ -34,7 +34,7 @@ module TS.SpaceTac.UI {
offset += 110;
active.forEach(mission => {
this.addMission(offset, mission.title, "Reward: ???", 0, () => null);
this.addMission(offset, mission, 0, () => null);
offset += 110;
});
}
@ -45,7 +45,7 @@ module TS.SpaceTac.UI {
offset += 110;
proposed.forEach(mission => {
this.addMission(offset, mission.title, "Reward: ???", 2, () => {
this.addMission(offset, mission, 2, () => {
this.shop.acceptMission(mission, this.player);
this.refresh();
this.on_change();
@ -58,13 +58,16 @@ module TS.SpaceTac.UI {
/**
* Add a mission text
*/
addMission(yoffset: number, title: string, subtitle: string, button_frame: number, button_callback: Function) {
addMission(yoffset: number, mission: Mission, button_frame: number, button_callback: Function) {
let title = mission.title;
let subtitle = `${capitalize(MissionDifficulty[mission.difficulty])} - Reward: ${mission.getRewardText()}`;
this.addImage(320, yoffset, "map-missions", 1);
if (title) {
this.addText(380, yoffset - 15, title, "#d2e1f3", 22, false, false, 620, true);
}
if (subtitle) {
this.addText(380, yoffset + 22, subtitle, "#d2e1f3", 20, false, false, 620, true);
this.addText(380, yoffset + 22, subtitle, "#d2e1f3", 18, false, false, 620, true);
}
this.addButton(1120, yoffset, button_callback, "map-mission-action", button_frame, button_frame + 1);
}

View File

@ -276,6 +276,7 @@ module TS.SpaceTac.UI {
this.setCamera(dest_star.x, dest_star.y, dest_star.radius * 2, duration, Phaser.Easing.Cubic.Out);
}, () => {
this.setInteractionEnabled(true);
this.refresh();
});
this.setInteractionEnabled(false);
}
@ -312,7 +313,7 @@ module TS.SpaceTac.UI {
moveToLocation(dest: StarLocation): void {
if (this.interactive && dest != this.player.fleet.location) {
this.setInteractionEnabled(false);
this.player_fleet.moveToLocation(dest, 1, null, () => this.updateInfo(dest.star));
this.player_fleet.moveToLocation(dest, 1, null, () => this.refresh());
}
}