Added equipment generation of different quality
This commit is contained in:
parent
5861deca6d
commit
5a5be8addc
3
TODO
3
TODO
|
@ -4,8 +4,7 @@
|
|||
* Character sheet: paginate loot and shop items
|
||||
* Character sheet: improve eye-catching for shop and loot section
|
||||
* Character sheet: equipping an item without the requirements make it disappear
|
||||
* Character sheet: display requirements on equipment tooltips
|
||||
* Shops: add equipment pricing, with usage depreciation
|
||||
* Shops: add price depreciation by item's previous usage
|
||||
* Add permanent effects to ship models to ease balancing
|
||||
* Ships should start battle in formation to force them to move
|
||||
* Fix targetting not resetting when using action shortcuts
|
||||
|
|
59
out/loot.html
Normal file
59
out/loot.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>SpaceTac - Loot Generator Samples</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="vendor/phaser/build/phaser.min.js"></script>
|
||||
<script src="build.js"></script>
|
||||
|
||||
<div id="loot">
|
||||
<h1>SpaceTac - Loot Generator Samples</h1>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var generator = new TS.SpaceTac.LootGenerator();
|
||||
generator.templates.forEach(function (template) {
|
||||
TS.range(5).forEach(function (level) {
|
||||
TS.iterenum(TS.SpaceTac.EquipmentQuality, function (quality) {
|
||||
var loot = template.generate(level + 1, quality);
|
||||
|
||||
let title = document.createElement("h2");
|
||||
title.textContent = loot.getFullName() + " (Price " + loot.price.toString() + ")";
|
||||
document.body.appendChild(title);
|
||||
|
||||
let description = document.createElement("pre");
|
||||
description.textContent = loot.getFullDescription();
|
||||
document.body.appendChild(description);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1 +1 @@
|
|||
Subproject commit 3092ff0f3b8af5fd1525480c5ec7033741cac100
|
||||
Subproject commit 78179da26a37232fcc25a2df9109dc9c2124ec76
|
|
@ -27,15 +27,18 @@ module TS.SpaceTac.Specs {
|
|||
0, // leave second ship alone
|
||||
0.95, // lucky loot on third ship
|
||||
0, // - lower end of level range (ship has 5, so range is 4-6)
|
||||
0.5, // - common quality
|
||||
0, // - take first generated equipment (there is only one anyway)
|
||||
0.96, // lucky loot on fourth ship
|
||||
0.999 // - higher end of level range
|
||||
0.999, // - higher end of level range
|
||||
0.98 // - premium quality
|
||||
]);
|
||||
|
||||
// Force lucky finds with one template
|
||||
var looter = new LootGenerator(random, false);
|
||||
var template = new LootTemplate(SlotType.Power, "Nuclear Reactor");
|
||||
template.setSkillsRequirements({ "skill_energy": istep(4) });
|
||||
template.addAttributeEffect("power_capacity", 1);
|
||||
looter.templates = [template];
|
||||
spyOn(outcome, "getLootGenerator").and.returnValue(looter);
|
||||
|
||||
|
@ -44,8 +47,12 @@ module TS.SpaceTac.Specs {
|
|||
expect(outcome.loot.length).toBe(3);
|
||||
expect(outcome.loot[0].name).toBe("0a");
|
||||
expect(outcome.loot[1].name).toBe("Nuclear Reactor");
|
||||
expect(outcome.loot[1].level).toBe(4);
|
||||
expect(outcome.loot[1].quality).toBe(EquipmentQuality.COMMON);
|
||||
expect(outcome.loot[1].requirements).toEqual({ "skill_energy": 7 });
|
||||
expect(outcome.loot[2].name).toBe("Nuclear Reactor");
|
||||
expect(outcome.loot[2].level).toBe(6);
|
||||
expect(outcome.loot[2].quality).toBe(EquipmentQuality.PREMIUM);
|
||||
expect(outcome.loot[2].requirements).toEqual({ "skill_energy": 9 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
module TS.SpaceTac {
|
||||
// Result of an ended battle
|
||||
/**
|
||||
* Result of an ended battle
|
||||
*
|
||||
* This stores the winner, and the retrievable loot
|
||||
*/
|
||||
export class BattleOutcome {
|
||||
// Indicates if the battle is a draw (no winner)
|
||||
draw: boolean;
|
||||
|
@ -45,17 +49,40 @@ module TS.SpaceTac {
|
|||
});
|
||||
}
|
||||
|
||||
// Create a loot generator for lucky loots
|
||||
/**
|
||||
* Create a loot generator for lucky finds
|
||||
*/
|
||||
getLootGenerator(random: RandomGenerator): LootGenerator {
|
||||
return new LootGenerator(random);
|
||||
}
|
||||
|
||||
// Generate a special loot item for the winner fleet
|
||||
// The equipment will be in the dead ship range
|
||||
/**
|
||||
* Generate a loot item for the winner fleet
|
||||
*
|
||||
* The equipment will be in the dead ship range
|
||||
*/
|
||||
generateLootItem(random: RandomGenerator, base_level: number): Equipment | null {
|
||||
var generator = this.getLootGenerator(random);
|
||||
var level = random.randInt(Math.max(base_level - 1, 1), base_level + 1);
|
||||
return generator.generate(level);
|
||||
let generator = this.getLootGenerator(random);
|
||||
let level = random.randInt(Math.max(base_level - 1, 1), base_level + 1);
|
||||
let quality = random.random();
|
||||
return generator.generate(level, this.getQuality(quality));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quality enum matching a 0-1 value
|
||||
*/
|
||||
getQuality(quality: number): EquipmentQuality {
|
||||
if (quality < 0.1) {
|
||||
return EquipmentQuality.WEAK;
|
||||
} else if (quality > 0.99) {
|
||||
return EquipmentQuality.LEGENDARY;
|
||||
} else if (quality > 0.95) {
|
||||
return EquipmentQuality.PREMIUM;
|
||||
} else if (quality > 0.8) {
|
||||
return EquipmentQuality.FINE;
|
||||
} else {
|
||||
return EquipmentQuality.COMMON;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ module TS.SpaceTac {
|
|||
name: string
|
||||
|
||||
// Equipment generic description
|
||||
description: string
|
||||
description = ""
|
||||
|
||||
// Indicative equipment level
|
||||
level = 1
|
||||
|
@ -33,14 +33,17 @@ module TS.SpaceTac {
|
|||
// Indicative equipment quality
|
||||
quality = EquipmentQuality.COMMON
|
||||
|
||||
// Base price
|
||||
price = 0
|
||||
|
||||
// Minimum skills to be able to equip this
|
||||
requirements: { [key: string]: number }
|
||||
requirements: { [key: string]: number } = {}
|
||||
|
||||
// Permanent effects on the ship that equips this
|
||||
effects: BaseEffect[]
|
||||
effects: BaseEffect[] = []
|
||||
|
||||
// Action available when equipped
|
||||
action: BaseAction
|
||||
action = new BaseAction("nothing", "Do nothing", false)
|
||||
|
||||
// Usage made of this equipment (will lower the sell price)
|
||||
usage: number
|
||||
|
@ -50,10 +53,6 @@ module TS.SpaceTac {
|
|||
this.slot_type = slot;
|
||||
this.code = code;
|
||||
this.name = code;
|
||||
this.description = "";
|
||||
this.requirements = {};
|
||||
this.effects = [];
|
||||
this.action = new BaseAction("nothing", "Do nothing", false);
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
|
@ -75,10 +74,20 @@ module TS.SpaceTac {
|
|||
* Get the full textual description for this equipment (without the full name).
|
||||
*/
|
||||
getFullDescription(): string {
|
||||
let requirements: string[] = [];
|
||||
iteritems(this.requirements, (skill, value) => {
|
||||
if (value > 0) {
|
||||
requirements.push(`- ${SHIP_ATTRIBUTES[skill].name} ${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
let description = this.getEffectsDescription();
|
||||
if (this.description) {
|
||||
description += "\n\n" + this.description;
|
||||
}
|
||||
if (requirements.length > 0) {
|
||||
description = "Requires:\n" + requirements.join("\n") + "\n\n" + description;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
|
@ -92,6 +101,13 @@ module TS.SpaceTac {
|
|||
return ShipLevel.getLevelForPoints(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the equipment price value.
|
||||
*/
|
||||
getPrice(): number {
|
||||
return this.price;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the equipment can be equipped on a ship.
|
||||
*
|
||||
|
|
|
@ -32,14 +32,13 @@ module TS.SpaceTac {
|
|||
}
|
||||
|
||||
// TODO Add generator from skills
|
||||
// TODO Add generator of other qualities
|
||||
|
||||
// Generate a random equipment for a specific level
|
||||
// If slot is specified, it will generate an equipment for this slot type specifically
|
||||
// If no equipment could be generated from available templates, null is returned
|
||||
generate(level: number, slot: SlotType | null = null): Equipment | null {
|
||||
generate(level: number, quality = EquipmentQuality.COMMON, slot: SlotType | null = null): Equipment | null {
|
||||
// Generate equipments matching conditions, with each template
|
||||
let equipments = this.templates.filter(template => slot == null || slot == template.slot).map(template => template.generate(level));
|
||||
let equipments = this.templates.filter(template => slot == null || slot == template.slot).map(template => template.generate(level, quality, this.random));
|
||||
|
||||
// No equipment could be generated with given conditions
|
||||
if (equipments.length === 0) {
|
||||
|
|
|
@ -17,9 +17,18 @@ module TS.SpaceTac.Specs {
|
|||
expect(result.slot_type).toEqual(SlotType.Power);
|
||||
expect(result.code).toEqual("powergenerator");
|
||||
expect(result.name).toEqual("Power Generator");
|
||||
expect(result.price).toEqual(300);
|
||||
expect(result.level).toEqual(2);
|
||||
expect(result.quality).toEqual(EquipmentQuality.PREMIUM);
|
||||
expect(result.quality).toEqual(EquipmentQuality.COMMON);
|
||||
expect(result.description).toEqual("A great power generator !");
|
||||
|
||||
template.addAttributeEffect("power_capacity", istep(10));
|
||||
result = template.generate(1, EquipmentQuality.COMMON);
|
||||
expect(result.quality).toEqual(EquipmentQuality.COMMON);
|
||||
expect(result.effects).toEqual([new AttributeEffect("power_capacity", 10)]);
|
||||
result = template.generate(1, EquipmentQuality.PREMIUM);
|
||||
expect(result.quality).toEqual(EquipmentQuality.PREMIUM);
|
||||
expect(result.effects).toEqual([new AttributeEffect("power_capacity", 13)]);
|
||||
});
|
||||
|
||||
it("applies requirements on skills", function () {
|
||||
|
|
|
@ -4,6 +4,12 @@ module TS.SpaceTac {
|
|||
*/
|
||||
type LeveledValue = number | Iterator<number>;
|
||||
|
||||
/**
|
||||
* Modifiers of generated equipment
|
||||
*/
|
||||
type QualityModifier = (equipment: Equipment, quality: EquipmentQuality, random: RandomGenerator) => boolean;
|
||||
type CommonModifier = (equipment: Equipment, level: number) => void;
|
||||
|
||||
/**
|
||||
* Resolve a leveled value
|
||||
*/
|
||||
|
@ -75,6 +81,84 @@ module TS.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic quality modifier
|
||||
*/
|
||||
function standardQualityModifier(equipment: Equipment, quality: EquipmentQuality, random: RandomGenerator): boolean {
|
||||
// Collect available modifiers
|
||||
let modifiers: Function[] = [];
|
||||
|
||||
let factor = 1;
|
||||
if (quality == EquipmentQuality.WEAK) {
|
||||
factor = 0.8;
|
||||
} else if (quality == EquipmentQuality.FINE) {
|
||||
factor = 1.1;
|
||||
} else if (quality == EquipmentQuality.PREMIUM) {
|
||||
factor = 1.3;
|
||||
} else if (quality == EquipmentQuality.LEGENDARY) {
|
||||
factor = 1.6;
|
||||
}
|
||||
|
||||
if (quality == EquipmentQuality.WEAK && any(values(equipment.requirements), value => value > 0)) {
|
||||
modifiers.push(() => {
|
||||
iteritems(copy(equipment.requirements), (skill, value) => {
|
||||
equipment.requirements[skill] = Math.max(equipment.requirements[skill] + 1, Math.floor(equipment.requirements[skill] / factor));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function simpleFactor<T>(obj: T, attr: keyof T, inverse = false) {
|
||||
let val = <any>obj[attr];
|
||||
if (val && val > 0) {
|
||||
let nval = Math.round((inverse ? (1 / factor) : factor) * val);
|
||||
if (nval != val) {
|
||||
modifiers.push(() => (<any>obj)[attr] = nval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function effectFactor(effect: BaseEffect) {
|
||||
if (effect instanceof ValueEffect || effect instanceof AttributeEffect) {
|
||||
simpleFactor(effect, 'value');
|
||||
} else if (effect instanceof AttributeLimitEffect) {
|
||||
simpleFactor(effect, 'value', true);
|
||||
} else if (effect instanceof StickyEffect) {
|
||||
simpleFactor(effect, 'duration');
|
||||
effectFactor(effect.base);
|
||||
} else if (effect instanceof DamageEffect) {
|
||||
simpleFactor(effect, 'value');
|
||||
}
|
||||
}
|
||||
|
||||
equipment.effects.forEach(effectFactor);
|
||||
|
||||
if (equipment.action instanceof FireWeaponAction) {
|
||||
simpleFactor(equipment.action, 'blast');
|
||||
simpleFactor(equipment.action, 'range');
|
||||
equipment.action.effects.forEach(effectFactor);
|
||||
}
|
||||
|
||||
if (equipment.action instanceof DeployDroneAction) {
|
||||
simpleFactor(equipment.action, 'deploy_distance');
|
||||
simpleFactor(equipment.action, 'effect_radius');
|
||||
equipment.action.effects.forEach(effectFactor);
|
||||
}
|
||||
|
||||
if (equipment.action instanceof MoveAction) {
|
||||
simpleFactor(equipment.action, 'distance_per_power');
|
||||
}
|
||||
|
||||
// Choose a random one
|
||||
if (modifiers.length > 0) {
|
||||
let chosen = random.choice(modifiers);
|
||||
chosen();
|
||||
equipment.price = Math.ceil(equipment.price * factor * factor);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template used to generate a loot equipment
|
||||
*/
|
||||
|
@ -88,14 +172,22 @@ module TS.SpaceTac {
|
|||
// Generic description of the equipment
|
||||
description: string
|
||||
|
||||
// Base price
|
||||
price: LeveledValue
|
||||
|
||||
// Modifiers applied to obtain the "common" equipment, based on level
|
||||
protected base_modifiers: ((equipment: Equipment, level: number) => void)[];
|
||||
protected base_modifiers: CommonModifier[]
|
||||
|
||||
// Modifiers applied to "common" equipment to obtain a specific quality
|
||||
protected quality_modifiers: QualityModifier[]
|
||||
|
||||
constructor(slot: SlotType, name: string, description = "") {
|
||||
this.slot = slot;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.price = istep(100, istep(200, irepeat(200)));
|
||||
this.base_modifiers = [];
|
||||
this.quality_modifiers = [standardQualityModifier];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,12 +197,19 @@ module TS.SpaceTac {
|
|||
let result = new Equipment(this.slot, (this.name || "").toLowerCase().replace(/ /g, ""));
|
||||
|
||||
result.level = level;
|
||||
result.quality = quality;
|
||||
result.name = this.name;
|
||||
result.description = this.description;
|
||||
result.price = resolveForLevel(this.price, level);
|
||||
|
||||
this.base_modifiers.forEach(modifier => modifier(result, level));
|
||||
|
||||
if (quality == EquipmentQuality.COMMON) {
|
||||
result.quality = quality;
|
||||
} else {
|
||||
let quality_applied = this.quality_modifiers.map(modifier => modifier(result, quality, random));
|
||||
result.quality = any(quality_applied, x => x) ? quality : EquipmentQuality.COMMON;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ module TS.SpaceTac {
|
|||
|
||||
// Fill equipment slots
|
||||
result.slots.forEach((slot: Slot) => {
|
||||
var equipment = loot.generate(level, slot.type);
|
||||
var equipment = loot.generate(level, EquipmentQuality.COMMON, slot.type);
|
||||
if (equipment) {
|
||||
slot.attach(equipment)
|
||||
if (slot.attached !== equipment) {
|
||||
|
|
|
@ -4,7 +4,7 @@ module TS.SpaceTac.Specs {
|
|||
let shop = new Shop();
|
||||
expect(shop.stock.length).toBe(0);
|
||||
|
||||
shop.generateStock(8);
|
||||
shop.generateStock(8, 1);
|
||||
expect(shop.stock.length).toBe(8);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,11 +8,17 @@ module TS.SpaceTac {
|
|||
|
||||
/**
|
||||
* Generate a random stock
|
||||
*
|
||||
* *level* is the preferential level, but equipment around it may be generated
|
||||
*/
|
||||
generateStock(items: number) {
|
||||
// TODO other levels
|
||||
let generator = new LootGenerator();
|
||||
this.stock = nna(range(items).map(i => generator.generate(1)));
|
||||
generateStock(items: number, level: number, random = RandomGenerator.global) {
|
||||
let generator = new LootGenerator(random);
|
||||
|
||||
this.stock = nna(range(items).map(() => {
|
||||
let equlevel = random.weighted(range(level + 3).map(i => i + 1).map(i => (i > level) ? 1 : i)) + 1;
|
||||
let quality = random.weighted([1, 7, 2]);
|
||||
return generator.generate(equlevel, quality);
|
||||
}));
|
||||
|
||||
this.sortStock();
|
||||
}
|
||||
|
@ -21,15 +27,14 @@ module TS.SpaceTac {
|
|||
* Sort the stock by equipment level, then by value
|
||||
*/
|
||||
sortStock() {
|
||||
// TODO
|
||||
this.stock.sort((a, b) => (a.level == b.level) ? cmp(a.getPrice(), b.getPrice()) : cmp(a.level, b.level));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buy/sell price for an equipment
|
||||
*/
|
||||
getPrice(equipment: Equipment): number {
|
||||
// TODO
|
||||
return 100;
|
||||
return equipment.getPrice();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -50,7 +50,7 @@ module TS.SpaceTac {
|
|||
addShop(generate_items = 0) {
|
||||
this.shop = new Shop();
|
||||
if (generate_items) {
|
||||
this.shop.generateStock(generate_items);
|
||||
this.shop.generateStock(generate_items, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue