New ship upgrade system, replacing equipments
66
README.md
|
@ -84,17 +84,16 @@ AI-piloted ships quickly colonized whole galaxies.
|
|||
|
||||
A ship gains experience during battles. When reaching a certain amount of experience points,
|
||||
a ship will automatically level up (which is, gain 1 level). Each level up will grant
|
||||
upgrade points that may be spent on Attributes.
|
||||
upgrade points that may be spent to unlock options.
|
||||
|
||||
A ship starts at level 1. There is no upper limit to level value (except 99, for display sake,
|
||||
but it may not be reached in a classic campaign).
|
||||
A ship starts at level 1, and may reach up to level 10.
|
||||
|
||||
### In-combat values (HSP)
|
||||
|
||||
In combat, a ship's vitals are represented by the HSP system (Hull-Shield-Power):
|
||||
|
||||
* **Hull** - Amount of damage that a ship can sustain before having to engage emergency stasis
|
||||
* **Shield** - Amount of damage that the shield equipments may absorb to protect the Hull
|
||||
* **Shield** - Amount of damage that the shields may absorb to protect the Hull
|
||||
* **Power** - Available action points (some actions require more power than others)
|
||||
|
||||
These values will be changed by various effects (usage of equipments, sustained damage...).
|
||||
|
@ -115,54 +114,12 @@ Attributes represent a ship's ability to use its HSP system and weapons:
|
|||
* **Maneuverability** - Ability to move first and fast
|
||||
* **Precision** - Ability to target far and good
|
||||
|
||||
These attributes are the sum of all currently applied effects (being permanent by an equipped item,
|
||||
or a temporary effect caused by a weapon or a drone).
|
||||
These attributes are the sum of all currently applied effects (permanent effects from the ship design,
|
||||
or temporary effects caused by a weapon or a drone).
|
||||
|
||||
For example, a ship that equips a power generator with "power generation +3", but has a sticky effect
|
||||
of "power generation -1" from a previous weapon hit, will have an effective power generation of 2.
|
||||
## Battle actions
|
||||
|
||||
### Skills
|
||||
|
||||
Skills represent a ship's ability to use equipments:
|
||||
|
||||
* **Materials** - Usage of physical materials such as bullets, shells...
|
||||
* **Photons** - Forces of light, and electromagnetic radiation
|
||||
* **Antimatter** - Manipulation of matter and antimatter particles
|
||||
* **Quantum** - Application of quantum uncertainty principle
|
||||
* **Gravity** - Interaction with gravitational forces
|
||||
* **Time** - Control of relativity's time properties
|
||||
|
||||
Each equipment has minimal skill requirements to be used. For example, a weapon may require "materials >= 2"
|
||||
and "photons >= 3" to be equipped. A ship that does not meet these requirements will not be able to use
|
||||
the equipment.
|
||||
|
||||
Skills are defined by the player, using points given while leveling up.
|
||||
As for attributes, skill values may also be altered by equipments.
|
||||
|
||||
If an equipped item has a requirement of "time skill >= 2", that the ship has "time skill" of exactly 2, and
|
||||
that a temporary effect of "time skill -1" is active, the requirement is no longer fulfilled and the equipped
|
||||
item is then temporarily disabled (no more effects and cannot be used), until the "time skill -1" effect is lifted.
|
||||
|
||||
## Equipments
|
||||
|
||||
### Overheat/Cooldown
|
||||
|
||||
Equipments may overheat, and need to cooldown for some time, during which it cannot be used.
|
||||
|
||||
If an equipment has "overheat 2 / cooldown 3", using it twice in the same turn will cause it to
|
||||
overheat. It then needs three "end of turns" to cool down and be available again. Using this equipment
|
||||
only once per turn is safe, and will never overheat it.
|
||||
|
||||
If an equipment has multiple actions associated, any of these actions will increase the shared heat.
|
||||
|
||||
*Not done yet :* Some equipments may have a "cumulative overheat", meaning that the heat is stored between turns,
|
||||
cooling down 1 point at the end of turn.
|
||||
|
||||
*Not done yet :* Some equipments may have a "stacked overheat", which
|
||||
is similar to "cumulative overheat", except it does not cool down at
|
||||
the end of turn (it will only start cooling down after being overheated).
|
||||
|
||||
## Drones
|
||||
### Drones
|
||||
|
||||
Drones are static objects, deployed by ships, that apply effects in a circular zone around themselves.
|
||||
|
||||
|
@ -173,12 +130,13 @@ Drones are fully autonomous, and once deployed, are not controlled by their owne
|
|||
|
||||
They are small and cannot be the direct target of weapons.
|
||||
|
||||
*Not done yet :* They are not affected by area effects,
|
||||
except for area damage and area effects specifically designed for drones.
|
||||
### Overheat/Cooldown
|
||||
|
||||
## Dockyards
|
||||
Equipments may overheat, and need to cooldown for some time, during which it cannot be used.
|
||||
|
||||
Dockyards are locations where ships can dock to buy or sell equipments, meet other ships and find jobs.
|
||||
If an action has "overheat 2 / cooldown 3", using it twice in the same turn will cause it to
|
||||
overheat. It then needs three "end of turns" to cool down and be available again. Using this action
|
||||
only once per turn is safe, and will never overheat it.
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
|
|
36
TODO.md
|
@ -8,6 +8,8 @@ Menu/settings/saves
|
|||
* Allow to delete cloud saves
|
||||
* Fix cloud save games with "Level 0 - 0 ships"
|
||||
* Store the game version in saves (for future work on compatibility)
|
||||
* Add simple options to quick battle (fleet level / difficulty)
|
||||
* Add optional fleet customization (both player and enemy) to quick battle
|
||||
|
||||
Map/story
|
||||
---------
|
||||
|
@ -17,39 +19,27 @@ Map/story
|
|||
* Allow to cancel secondary missions
|
||||
* Forbid to end up with more than 5 ships in the fleet because of escorts
|
||||
* Fix problems when several dialogs are active at the same time
|
||||
* Handle case where cargo is full to give a reward (give money?)
|
||||
* Add a zoom level, to see the location only
|
||||
|
||||
Character sheet
|
||||
---------------
|
||||
|
||||
* Add a randomization button in creation view
|
||||
* Fix the hover/on not working on fleet members
|
||||
* Improve tooltip content
|
||||
* Replace the close icon by a validation icon in creation view
|
||||
* Allow to cancel spent skill points (and confirm when closing the sheet)
|
||||
* Highlight matched/unmatched skills when dragging an equipment to a slot
|
||||
* Highlight attribute changes when dragging an equipment to a slot
|
||||
* Propose to auto upgrade skills if enough points are available, when equipping an equipment with unmatched skills
|
||||
* Improve eye-catching for shop and loot section
|
||||
* Highlight allowed destinations during drag-and-drop
|
||||
* Effective skill is sometimes not updated when upgrading base skill
|
||||
* Add merged cargo display for the whole fleet
|
||||
* Allow to change/buy ship model
|
||||
* Allow to rename a personality (in creation view only)
|
||||
* Add personality indicators (editable in creation view)
|
||||
* Add filters and sort options for cargo and shop
|
||||
* Display level and slot type on equipment
|
||||
* Fixed tooltips not being visible in loot mode (at the end of battle)
|
||||
|
||||
Battle
|
||||
------
|
||||
|
||||
* Add a voluntary retreat option
|
||||
* Add scroll buttons when there are too many actions
|
||||
* Toggle bar/text display in power section of action bar
|
||||
* Display effects description instead of attribute changes
|
||||
* Show a cooldown indicator on move action icon, if the simulation would cause the engine to overheat
|
||||
* Add engine trail effect, and sound
|
||||
* Allow to skip animations, and allow no animation mode
|
||||
* Find incentives to move from starting position (permanent drones or anomalies?)
|
||||
* Add a "loot all" button (on the character sheet or outcome dialog?)
|
||||
* Mark targetting in error when target is refused by the action (there is already an arrow for this)
|
||||
* Allow to undo last moves
|
||||
* Add a battle log display
|
||||
|
@ -61,10 +51,9 @@ Battle
|
|||
* Add a turn count marker in the ship list
|
||||
* BattleChecks should be done proactively when all diffs have been simulated by an action, in addition to reactively after applying
|
||||
|
||||
Ships models and equipments
|
||||
---------------------------
|
||||
Ships models and actions
|
||||
------------------------
|
||||
|
||||
* Add permanent effects and actions to ship models
|
||||
* Add critical hit/miss (or indicate lucky/unlucky throws)
|
||||
* Add damage over time effect (tricky to make intuitive)
|
||||
* Add actions with cost dependent of distance (like current move actions)
|
||||
|
@ -73,11 +62,14 @@ Ships models and equipments
|
|||
* Add mines equivalent (drones that apply only at the end)
|
||||
* RepelEffect should apply on ships in a good order (distance decreasing)
|
||||
* Add hull points to drones and make them take area damage
|
||||
* Quality modifiers should be based on an "quality difference" to reach
|
||||
* Add a target type filter (all, enemies, allies, self or not)
|
||||
* Shields should be able to absorb (some type of) damage, even with 1 remaining
|
||||
* Add a balance testing page, using AI battles with or without an upgrade, to help in balancing
|
||||
|
||||
Artificial Intelligence
|
||||
-----------------------
|
||||
|
||||
* Fix tendency to use moves for nothing
|
||||
* Produce interesting "angle" areas
|
||||
* Evaluate active effects
|
||||
* Account for luck
|
||||
|
@ -92,7 +84,8 @@ Artificial Intelligence
|
|||
Common UI
|
||||
---------
|
||||
|
||||
* UIBuilder.button should be able to handle hover and pushed images
|
||||
* Fix calling setHoverClick several times on the same button not working as expected
|
||||
* Fix tooltip remaining when the hovered object is hidden by animations
|
||||
* If ProgressiveMessage animation performance is bad, show the text directly
|
||||
* Add caret/focus to text input
|
||||
* Mobile: think UI layout so that fingers do not block the view (right and left handed)
|
||||
|
@ -105,7 +98,6 @@ Technical
|
|||
* Pack all images in atlases, and split them by stage
|
||||
* Pack sounds
|
||||
* Add toggles for shaders, automatically disable them if too slow, and initially disable them on mobile
|
||||
* Replace jasmine with mocha+chai
|
||||
|
||||
Network
|
||||
-------
|
||||
|
|
BIN
graphics/exported/action/damageprotector.png
Normal file
After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 5 KiB |
BIN
graphics/exported/action/precisionboost.png
Normal file
After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1 KiB |
BIN
graphics/exported/character/close-button-hover.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
graphics/exported/character/close-button.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.1 KiB |
BIN
graphics/exported/character/entry.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 323 B |
BIN
graphics/exported/character/initial.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
graphics/exported/character/level-display.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
graphics/exported/character/level-experience.png
Normal file
After Width: | Height: | Size: 849 B |
BIN
graphics/exported/character/level-separator.png
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
graphics/exported/character/level-upgrades.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
graphics/exported/character/name-button-hover.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
graphics/exported/character/name-button.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
graphics/exported/character/name-display.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
graphics/exported/character/portrait-hover.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
graphics/exported/character/portrait-on.png
Normal file
After Width: | Height: | Size: 474 B |
BIN
graphics/exported/character/portrait.png
Normal file
After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.7 KiB |
BIN
graphics/exported/character/ship-column.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
graphics/exported/character/ship-description.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
graphics/exported/character/ship-model.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
BIN
graphics/exported/character/upgrade-hover.png
Normal file
After Width: | Height: | Size: 463 B |
BIN
graphics/exported/character/upgrade-locked.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
graphics/exported/character/upgrade-on.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
graphics/exported/character/upgrade-point.png
Normal file
After Width: | Height: | Size: 373 B |
BIN
graphics/exported/character/upgrade.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 55 KiB |
BIN
graphics/exported/map/options-hover.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
graphics/exported/map/options.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
graphics/exported/map/zoom-in-hover.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
graphics/exported/map/zoom-in.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
graphics/exported/map/zoom-out-hover.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
graphics/exported/map/zoom-out.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
|
@ -16,7 +16,7 @@
|
|||
version="1.1"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="actions.svg"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/equipment/kelvingenerator.png"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/action/damageprotector.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90"
|
||||
viewBox="0 0 256 256"
|
||||
|
@ -3114,7 +3114,7 @@
|
|||
inkscape:groupmode="layer"
|
||||
id="layer11"
|
||||
inkscape:label="DamageProtector"
|
||||
style="display:none">
|
||||
style="display:inline">
|
||||
<circle
|
||||
style="fill:url(#radialGradient4955);fill-opacity:1;fill-rule:evenodd;stroke:#6c6c6c;stroke-width:7.49999952;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.8749999, 1.8749999;stroke-dashoffset:0;stroke-opacity:0.57234042"
|
||||
id="path4921"
|
||||
|
@ -3430,7 +3430,7 @@
|
|||
inkscape:groupmode="layer"
|
||||
id="layer14"
|
||||
inkscape:label="KelvinGenerator"
|
||||
style="display:inline">
|
||||
style="display:none">
|
||||
<g
|
||||
id="g4987"
|
||||
transform="matrix(0.77942184,0,0,0.77942184,28.096496,22.946262)">
|
||||
|
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
@ -16,7 +16,7 @@
|
|||
viewBox="0 0 1920 1080"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.2 (unknown)"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="battle.svg"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/battle/actionbar/power-generated.png"
|
||||
inkscape:export-xdpi="96"
|
||||
|
@ -3200,6 +3200,42 @@
|
|||
y1="123.97214"
|
||||
x2="111.73327"
|
||||
y2="123.97214" />
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB;"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter6088"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1.1000000000000001"
|
||||
height="1.1000000000000001">
|
||||
<feFlood
|
||||
flood-opacity="0.564706"
|
||||
flood-color="rgb(64,68,80)"
|
||||
result="flood"
|
||||
id="feFlood6078" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite6080" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="2"
|
||||
result="blur"
|
||||
id="feGaussianBlur6082" />
|
||||
<feOffset
|
||||
dx="4"
|
||||
dy="4"
|
||||
result="offset"
|
||||
id="feOffset6084" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite6086" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
|
@ -3209,8 +3245,8 @@
|
|||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="1317.1113"
|
||||
inkscape:cy="451.49885"
|
||||
inkscape:cx="1342.5671"
|
||||
inkscape:cy="597.16284"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g7473"
|
||||
showgrid="false"
|
||||
|
@ -7793,13 +7829,13 @@
|
|||
<rect
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-filename="/home/michael/workspace/spacetac/graphics/exported/battle/tooltip/ship-portrait.png"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/battle/tooltip/ship-portrait.png"
|
||||
y="492.55179"
|
||||
x="460.07184"
|
||||
height="193.97655"
|
||||
width="193.97672"
|
||||
id="rect4260-1"
|
||||
style="display:inline;opacity:1;fill:#43535c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.5999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter6021-5);enable-background:new" />
|
||||
style="display:inline;opacity:1;fill:#43535c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.5999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new;filter:url(#filter6088)" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
|
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 373 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 94 KiB |
|
@ -804,7 +804,7 @@
|
|||
height="1.0909113">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.87056823"
|
||||
stdDeviation="0.87"
|
||||
id="feGaussianBlur5519" />
|
||||
</filter>
|
||||
<filter
|
||||
|
@ -1090,19 +1090,6 @@
|
|||
result="composite2"
|
||||
id="feComposite6295" />
|
||||
</filter>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter6390"
|
||||
x="-0.0936"
|
||||
width="1.1872"
|
||||
y="-0.0936"
|
||||
height="1.1872">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="1.1866562"
|
||||
id="feGaussianBlur6392" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
|
@ -1111,11 +1098,11 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.70710682"
|
||||
inkscape:cx="728.12591"
|
||||
inkscape:cy="574.187"
|
||||
inkscape:zoom="1.175"
|
||||
inkscape:cx="1702.4952"
|
||||
inkscape:cy="235.31089"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g6210"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
|
@ -1625,9 +1612,9 @@
|
|||
style="display:inline">
|
||||
<g
|
||||
id="g5061"
|
||||
transform="translate(85.195844,18.256263)"
|
||||
transform="translate(167.74587,18.256263)"
|
||||
style="display:inline;filter:url(#filter5500)"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/map/zoom-in-hover.png"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/map/zoom-in.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<g
|
||||
|
@ -1654,9 +1641,9 @@
|
|||
style="display:inline"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-filename="/tmp/temp.png"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/map/options.png"
|
||||
id="g4766"
|
||||
transform="rotate(-90,274.10252,151.75608)">
|
||||
transform="rotate(-90,315.37748,110.48112)">
|
||||
<g
|
||||
id="g5998">
|
||||
<use
|
||||
|
@ -1704,10 +1691,10 @@
|
|||
<g
|
||||
style="display:inline"
|
||||
id="g5335"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/map/zoom-out-hover.png"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/map/zoom-out.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
transform="translate(0,-4.2333336)">
|
||||
transform="translate(82.550003,-4.2333336)">
|
||||
<use
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:export-xdpi="96"
|
||||
|
@ -1948,9 +1935,9 @@
|
|||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5014-3"
|
||||
d="M 407.58479,68.772624 395.03383,56.257748 V 35.322624 l 12.55096,-12.514874 12.55096,12.514874 v 20.935124 z"
|
||||
d="M 490.13483,68.772624 477.58387,56.257748 V 35.322624 l 12.55096,-12.514874 12.55096,12.514874 v 20.935124 z"
|
||||
style="display:inline;fill:#93aac7;fill-opacity:0.57021281;fill-rule:evenodd;stroke:none;stroke-width:0.69788885;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5517)"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/map/zoom-in-hover.png"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/map/zoom-in-hover.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<use
|
||||
|
@ -1960,8 +1947,8 @@
|
|||
id="use4855"
|
||||
width="100%"
|
||||
height="100%"
|
||||
transform="translate(-5.6259523e-6,207.69819)"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/map/zoom-out-hover.png"
|
||||
transform="translate(-5.6107176e-6,207.69819)"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/map/zoom-out-hover.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<rect
|
||||
|
@ -1978,8 +1965,8 @@
|
|||
id="use6000"
|
||||
width="100%"
|
||||
height="100%"
|
||||
transform="rotate(90,407.58466,18.273681)"
|
||||
inkscape:export-filename="/tmp/temp.png"
|
||||
transform="rotate(90,490.13458,18.273681)"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/graphics/exported/map/options-hover.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
</g>
|
||||
|
@ -2192,31 +2179,12 @@
|
|||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Character sheet">
|
||||
<g
|
||||
transform="translate(0,-11.249983)"
|
||||
id="g4931">
|
||||
<rect
|
||||
y="11.249983"
|
||||
x="426.50833"
|
||||
height="285.75"
|
||||
width="81.491669"
|
||||
id="rect4514"
|
||||
style="opacity:1;fill:#20262e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.2854228;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="37.4282"
|
||||
x="433.63654"
|
||||
height="67.235291"
|
||||
width="67.235291"
|
||||
id="rect4926"
|
||||
style="fill:#43535c;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.23345587px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
inkscape:label="Character sheet" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="Mission dialog"
|
||||
style="display:inline">
|
||||
style="display:none">
|
||||
<g
|
||||
id="g6210"
|
||||
transform="translate(-4.1823501,30.369099)">
|
||||
|
|
Before Width: | Height: | Size: 412 KiB After Width: | Height: | Size: 411 KiB |
BIN
graphics/ui/pics/action.png
Normal file
After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
graphics/ui/pics/portrait.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
graphics/ui/pics/sprite.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 24 KiB |
129
out/loot.html
|
@ -1,129 +0,0 @@
|
|||
<!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;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
position: relative;
|
||||
margin-left: 1em;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
input[type="range"]:after,
|
||||
input[type="range"]:before {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
input[type="range"]:before {
|
||||
left: 0em;
|
||||
content: attr(min);
|
||||
}
|
||||
|
||||
input[type="range"]:after {
|
||||
right: 0em;
|
||||
content: attr(max);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="vendor/phaser/phaser.min.js"></script>
|
||||
<script src="build.js"></script>
|
||||
|
||||
<div id="loot">
|
||||
<h1>SpaceTac - Loot Generator Samples</h1>
|
||||
<select id="template">
|
||||
<option value="---">---</option>
|
||||
</select>
|
||||
<input id="level" type="range" value="1" min="1" max="20" step="1" />
|
||||
<button id="refresh" type="button">Refresh</button>
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var generator = new TK.SpaceTac.LootGenerator();
|
||||
var result = document.getElementById("result");
|
||||
var current_level = 1;
|
||||
var current_name = "";
|
||||
var qualities = {}
|
||||
|
||||
qualities[TK.SpaceTac.EquipmentQuality.WEAK] = "#e66";
|
||||
qualities[TK.SpaceTac.EquipmentQuality.COMMON] = "#eee";
|
||||
qualities[TK.SpaceTac.EquipmentQuality.FINE] = "#669";
|
||||
qualities[TK.SpaceTac.EquipmentQuality.PREMIUM] = "#66b";
|
||||
qualities[TK.SpaceTac.EquipmentQuality.LEGENDARY] = "#66e";
|
||||
|
||||
function update() {
|
||||
result.innerHTML = "";
|
||||
generator.templates.forEach(function (template) {
|
||||
if (template.name != current_name) {
|
||||
return;
|
||||
}
|
||||
TK.iterenum(TK.SpaceTac.EquipmentQuality, function (quality) {
|
||||
var loot = template.generate(current_level, quality);
|
||||
|
||||
var block = document.createElement("div");
|
||||
block.setAttribute("style", "color:" + qualities[quality]);
|
||||
result.appendChild(block);
|
||||
|
||||
var title = document.createElement("h2");
|
||||
title.textContent = loot.getFullName() + " (Price " + loot.price.toString() + ")";
|
||||
block.appendChild(title);
|
||||
|
||||
var description = document.createElement("pre");
|
||||
description.textContent = loot.getFullDescription();
|
||||
block.appendChild(description);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TK.sortedBy(generator.templates, function (template) {
|
||||
return template.name;
|
||||
}).forEach(function (template) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = template.name;
|
||||
opt.innerHTML = template.name;
|
||||
document.getElementById("template").appendChild(opt);
|
||||
});
|
||||
|
||||
document.getElementById("level").onchange = function () {
|
||||
current_level = this.value;
|
||||
update();
|
||||
}
|
||||
document.getElementById("template").onchange = function () {
|
||||
current_name = this.value;
|
||||
update();
|
||||
}
|
||||
document.getElementById("refresh").onclick = function () {
|
||||
update();
|
||||
}
|
||||
update();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1 +1 @@
|
|||
Subproject commit 38c06700cfb3d03a1f7309a6200ab0ca7c0ee9d8
|
||||
Subproject commit 2882c791e8af845517fd030c6e789296d347213d
|
|
@ -68,7 +68,7 @@ module TK.SpaceTac {
|
|||
var ship3 = new Ship(fleet2, "ship3");
|
||||
|
||||
var battle = new Battle(fleet1, fleet2);
|
||||
battle.ships.list().forEach(ship => TestTools.setShipHP(ship, 10, 0));
|
||||
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 10, 0));
|
||||
|
||||
// Check empty play_order case
|
||||
check.equals(battle.playing_ship, null);
|
||||
|
@ -118,7 +118,7 @@ module TK.SpaceTac {
|
|||
check.equals(battle.ships.list().filter(ship => ship.alive), [ship1, ship2, ship3, ship4], "alive ships");
|
||||
});
|
||||
|
||||
let result = battle.applyOneAction(nn(weapon.action).id, Target.newFromLocation(0, 0));
|
||||
let result = battle.applyOneAction(weapon.id, Target.newFromLocation(0, 0));
|
||||
check.equals(result, true, "action applied successfully");
|
||||
check.in("after weapon", check => {
|
||||
check.same(battle.playing_ship, ship3, "playing ship");
|
||||
|
@ -135,7 +135,7 @@ module TK.SpaceTac {
|
|||
let ship3 = new Ship(fleet2, "F2S1");
|
||||
|
||||
var battle = new Battle(fleet1, fleet2);
|
||||
battle.ships.list().forEach(ship => TestTools.setShipHP(ship, 10, 0));
|
||||
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 10, 0));
|
||||
battle.start();
|
||||
battle.play_order = [ship3, ship2, ship1];
|
||||
check.equals(battle.ended, false);
|
||||
|
@ -154,41 +154,6 @@ module TK.SpaceTac {
|
|||
}
|
||||
});
|
||||
|
||||
test.case("wear down equipment at the end of battle", check => {
|
||||
let fleet1 = new Fleet();
|
||||
let ship1a = fleet1.addShip();
|
||||
let equ1a = TestTools.addWeapon(ship1a);
|
||||
let ship1b = fleet1.addShip();
|
||||
let equ1b = TestTools.addWeapon(ship1b);
|
||||
let fleet2 = new Fleet();
|
||||
let ship2a = fleet2.addShip();
|
||||
let equ2a = TestTools.addWeapon(ship2a);
|
||||
let eng2a = TestTools.addEngine(ship2a, 50);
|
||||
|
||||
let battle = new Battle(fleet1, fleet2);
|
||||
battle.ships.list().forEach(ship => TestTools.setShipHP(ship, 10, 0));
|
||||
battle.start();
|
||||
|
||||
check.equals(equ1a.wear, 0);
|
||||
check.equals(equ1b.wear, 0);
|
||||
check.equals(equ2a.wear, 0);
|
||||
check.equals(eng2a.wear, 0);
|
||||
|
||||
range(8).forEach(() => battle.advanceToNextShip());
|
||||
|
||||
check.equals(equ1a.wear, 0);
|
||||
check.equals(equ1b.wear, 0);
|
||||
check.equals(equ2a.wear, 0);
|
||||
check.equals(eng2a.wear, 0);
|
||||
|
||||
battle.endBattle(null);
|
||||
|
||||
check.equals(equ1a.wear, 3);
|
||||
check.equals(equ1b.wear, 3);
|
||||
check.equals(equ2a.wear, 3);
|
||||
check.equals(eng2a.wear, 3);
|
||||
});
|
||||
|
||||
test.case("handles a draw in end battle", check => {
|
||||
var fleet1 = new Fleet();
|
||||
var fleet2 = new Fleet();
|
||||
|
@ -316,7 +281,7 @@ module TK.SpaceTac {
|
|||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
|
||||
check.equals(imaterialize(battle.iAreaEffects(100, 50)), []);
|
||||
check.equals(imaterialize(battle.iAreaEffects(100, 50)), [], "initial");
|
||||
|
||||
let drone1 = new Drone(ship);
|
||||
drone1.x = 120;
|
||||
|
@ -331,22 +296,22 @@ module TK.SpaceTac {
|
|||
drone2.effects = [new DamageEffect(14)];
|
||||
battle.addDrone(drone2);
|
||||
|
||||
check.equals(imaterialize(battle.iAreaEffects(100, 50)), [drone1.effects[0]]);
|
||||
check.equals(imaterialize(battle.iAreaEffects(100, 50)), [drone1.effects[0]], "drone effects");
|
||||
|
||||
let eq1 = ship.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
|
||||
eq1.action = new ToggleAction(eq1, 0, 500, [new AttributeEffect("maneuvrability", 1)]);
|
||||
(<ToggleAction>eq1.action).activated = true;
|
||||
let eq2 = ship.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
|
||||
eq2.action = new ToggleAction(eq2, 0, 500, [new AttributeEffect("maneuvrability", 2)]);
|
||||
(<ToggleAction>eq2.action).activated = false;
|
||||
let eq3 = ship.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
|
||||
eq3.action = new ToggleAction(eq3, 0, 100, [new AttributeEffect("maneuvrability", 3)]);
|
||||
(<ToggleAction>eq3.action).activated = true;
|
||||
let eq1 = new ToggleAction("eq1", { power: 0, radius: 500, effects: [new AttributeEffect("maneuvrability", 1)] });
|
||||
ship.actions.addCustom(eq1);
|
||||
ship.actions.toggle(eq1, true);
|
||||
let eq2 = new ToggleAction("eq2", { power: 0, radius: 500, effects: [new AttributeEffect("maneuvrability", 2)] });
|
||||
ship.actions.addCustom(eq2);
|
||||
ship.actions.toggle(eq2, false);
|
||||
let eq3 = new ToggleAction("eq3", { power: 0, radius: 100, effects: [new AttributeEffect("maneuvrability", 3)] });
|
||||
ship.actions.addCustom(eq3);
|
||||
ship.actions.toggle(eq3, true);
|
||||
|
||||
check.equals(imaterialize(battle.iAreaEffects(100, 50)), [
|
||||
drone1.effects[0],
|
||||
(<ToggleAction>eq1.action).effects[0],
|
||||
]);
|
||||
eq1.effects[0],
|
||||
], "drone and toggle effects");
|
||||
});
|
||||
|
||||
test.case("is serializable", check => {
|
||||
|
|
|
@ -274,9 +274,8 @@ module TK.SpaceTac {
|
|||
this.outcome = null;
|
||||
this.cycle = 1;
|
||||
this.placeShips();
|
||||
this.stats.addFleetsValue(this.fleets[0], this.fleets[1]);
|
||||
this.throwInitiative();
|
||||
iforeach(this.iships(), ship => ship.restoreInitialState());
|
||||
this.throwInitiative();
|
||||
this.setPlayingShip(this.play_order[0]);
|
||||
}
|
||||
|
||||
|
@ -354,7 +353,7 @@ module TK.SpaceTac {
|
|||
applyOneAction(action_id: RObjectId, target?: Target): boolean {
|
||||
let ship = this.playing_ship;
|
||||
if (ship) {
|
||||
let action = ship.getAction(action_id);
|
||||
let action = ship.actions.getById(action_id);
|
||||
if (action) {
|
||||
if (!target) {
|
||||
target = action.getDefaultTarget(ship);
|
||||
|
|
|
@ -21,21 +21,5 @@ module TK.SpaceTac.Specs {
|
|||
check.same(nn(battle.outcome).winner, battle.fleets[1], "winner");
|
||||
check.equals(any(battle.fleets[0].ships, ship => ship.alive), false, "all allies dead");
|
||||
})
|
||||
|
||||
test.case("adds an equipment", check => {
|
||||
let battle = new Battle();
|
||||
let ship = new Ship();
|
||||
TestTools.setShipPlaying(battle, ship);
|
||||
ship.upgradeSkill("skill_materials");
|
||||
let cheats = new BattleCheats(battle, battle.fleets[0].player);
|
||||
|
||||
check.equals(ship.listEquipment(), []);
|
||||
|
||||
cheats.equip("Iron Hull");
|
||||
|
||||
let result = ship.listEquipment();
|
||||
check.equals(result.length, 1);
|
||||
check.containing(result[0], { name: "Iron Hull", level: 1 });
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,23 +36,5 @@ module TK.SpaceTac {
|
|||
});
|
||||
this.battle.endBattle(first(this.battle.fleets, fleet => !this.player.is(fleet.player)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an equipment to current playing ship
|
||||
*/
|
||||
equip(name: string): void {
|
||||
let ship = this.battle.playing_ship;
|
||||
if (ship) {
|
||||
let generator = new LootGenerator();
|
||||
generator.setTemplateFilter(template => template.name == name);
|
||||
|
||||
let equipment = generator.generateHighest(ship.skills);
|
||||
if (equipment) {
|
||||
let slot_type = nn(equipment.slot_type);
|
||||
let slot = ship.getFreeSlot(slot_type) || ship.addSlot(slot_type);
|
||||
slot.attach(equipment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +1,5 @@
|
|||
module TK.SpaceTac.Specs {
|
||||
testing("BattleOutcome", test => {
|
||||
test.case("generates loot from defeated ships", check => {
|
||||
var fleet1 = new Fleet();
|
||||
fleet1.addShip(new Ship());
|
||||
var fleet2 = new Fleet();
|
||||
fleet2.addShip(new Ship());
|
||||
fleet2.addShip(new Ship());
|
||||
fleet2.addShip(new Ship());
|
||||
fleet2.addShip(new Ship());
|
||||
|
||||
fleet2.ships[2].level.forceLevel(5);
|
||||
fleet2.ships[3].level.forceLevel(5);
|
||||
|
||||
fleet2.ships[0].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "0a"));
|
||||
fleet2.ships[0].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "0b"));
|
||||
fleet2.ships[1].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "1a"));
|
||||
fleet2.ships[2].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "2b"));
|
||||
fleet2.ships[3].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "3b"));
|
||||
|
||||
var battle = new Battle(fleet1, fleet2);
|
||||
var outcome = new BattleOutcome(fleet1);
|
||||
|
||||
var random = new SkewedRandomGenerator([
|
||||
0.6, // standard loot on first ship
|
||||
0, // - take first equipment
|
||||
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.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_photons": istep(4) });
|
||||
template.addAttributeEffect("power_capacity", istep(1));
|
||||
looter.templates = [template];
|
||||
check.patch(outcome, "getLootGenerator", () => looter);
|
||||
|
||||
outcome.createLoot(battle, random);
|
||||
|
||||
check.equals(outcome.loot.length, 3);
|
||||
check.equals(outcome.loot[0].name, "0a");
|
||||
check.equals(outcome.loot[1].name, "Nuclear Reactor");
|
||||
check.equals(outcome.loot[1].level, 4);
|
||||
check.same(outcome.loot[1].quality, EquipmentQuality.COMMON);
|
||||
check.equals(outcome.loot[1].requirements, { "skill_photons": 7 });
|
||||
check.equals(outcome.loot[2].name, "Nuclear Reactor");
|
||||
check.equals(outcome.loot[2].level, 6);
|
||||
check.same(outcome.loot[2].quality, EquipmentQuality.PREMIUM);
|
||||
check.equals(outcome.loot[2].requirements, { "skill_photons": 9 });
|
||||
});
|
||||
|
||||
test.case("grants experience", check => {
|
||||
let fleet1 = new Fleet();
|
||||
let ship1a = fleet1.addShip(new Ship());
|
||||
|
|
|
@ -6,47 +6,14 @@ module TK.SpaceTac {
|
|||
*/
|
||||
export class BattleOutcome {
|
||||
// Indicates if the battle is a draw (no winner)
|
||||
draw: boolean;
|
||||
draw: boolean
|
||||
|
||||
// Victorious fleet
|
||||
winner: Fleet | null;
|
||||
|
||||
// Retrievable loot
|
||||
loot: Equipment[];
|
||||
winner: Fleet | null
|
||||
|
||||
constructor(winner: Fleet | null) {
|
||||
this.winner = winner;
|
||||
this.draw = winner ? false : true;
|
||||
this.loot = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill loot from defeated fleet
|
||||
*/
|
||||
createLoot(battle: Battle, random = RandomGenerator.global): void {
|
||||
this.loot = [];
|
||||
|
||||
battle.fleets.forEach(fleet => {
|
||||
if (this.winner && !this.winner.player.is(fleet.player)) {
|
||||
fleet.ships.forEach(ship => {
|
||||
var luck = random.random();
|
||||
if (luck > 0.9) {
|
||||
// Salvage a supposedly transported item
|
||||
var transported = this.generateLootItem(random, ship.level.get());
|
||||
if (transported) {
|
||||
this.loot.push(transported);
|
||||
}
|
||||
} else if (luck > 0.5) {
|
||||
// Salvage one equipped item
|
||||
var token = ship.getRandomEquipment(random);
|
||||
if (token) {
|
||||
token.detach();
|
||||
this.loot.push(token);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,41 +30,5 @@ module TK.SpaceTac {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a loot generator for lucky finds
|
||||
*/
|
||||
getLootGenerator(random: RandomGenerator): LootGenerator {
|
||||
return new LootGenerator(random);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,26 +71,5 @@ module TK.SpaceTac.Specs {
|
|||
stats.processLog(battle.log, battle.fleets[0], true);
|
||||
check.equals(stats.stats, { "Drones deployed": [1, 1] });
|
||||
})
|
||||
|
||||
test.case("evaluates equipment depreciation", check => {
|
||||
let stats = new BattleStats();
|
||||
let battle = new Battle();
|
||||
let attacker = battle.fleets[0].addShip();
|
||||
let defender = battle.fleets[1].addShip();
|
||||
|
||||
let equ1 = TestTools.addEngine(attacker, 50);
|
||||
equ1.price = 1000;
|
||||
let equ2 = TestTools.addEngine(defender, 50);
|
||||
equ2.price = 1100;
|
||||
|
||||
stats.addFleetsValue(attacker.fleet, defender.fleet);
|
||||
check.equals(stats.stats, { "Equipment wear (zotys)": [1000, 1100] });
|
||||
|
||||
equ1.price = 500;
|
||||
equ2.price = 800;
|
||||
|
||||
stats.addFleetsValue(attacker.fleet, defender.fleet, false);
|
||||
check.equals(stats.stats, { "Equipment wear (zotys)": [500, 300] });
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -58,23 +58,5 @@ module TK.SpaceTac {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw value of a fleet
|
||||
*/
|
||||
private getFleetValue(fleet: Fleet): number {
|
||||
return sum(fleet.ships.map(ship => {
|
||||
return sum(ship.listEquipment().map(equipment => equipment.getPrice()));
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the fleets' value, for equipment wear display
|
||||
*/
|
||||
addFleetsValue(attacker: Fleet, defender: Fleet, positive = true): void {
|
||||
let sgn = positive ? 1 : -1;
|
||||
this.addStat("Equipment wear (zotys)", sgn * this.getFleetValue(attacker), true);
|
||||
this.addStat("Equipment wear (zotys)", sgn * this.getFleetValue(defender), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,17 @@ module TK.SpaceTac {
|
|||
test.case("applies area effects when deployed", check => {
|
||||
let battle = TestTools.createBattle();
|
||||
let ship = nn(battle.playing_ship);
|
||||
TestTools.setShipAP(ship, 10);
|
||||
let weapon = TestTools.addWeapon(ship);
|
||||
weapon.action = new DeployDroneAction(weapon, 2, 300, 30, [new AttributeEffect("precision", 15)]);
|
||||
TestTools.setShipModel(ship, 100, 0, 10);
|
||||
let weapon = new DeployDroneAction("testdrone", { power: 2 }, { deploy_distance: 300, drone_radius: 30, drone_effects: [new AttributeEffect("precision", 15)] });
|
||||
ship.actions.addCustom(weapon);
|
||||
let engine = TestTools.addEngine(ship, 1000);
|
||||
|
||||
TestTools.actionChain(check, battle, [
|
||||
[ship, nn(weapon.action), Target.newFromLocation(150, 50)], // deploy out of effects radius
|
||||
[ship, nn(engine.action), Target.newFromLocation(110, 50)], // move out of effects radius
|
||||
[ship, nn(engine.action), Target.newFromLocation(130, 50)], // move in effects radius
|
||||
[ship, nn(weapon.action), Target.newFromShip(ship)], // recall
|
||||
[ship, nn(weapon.action), Target.newFromLocation(130, 70)], // deploy in effects radius
|
||||
[ship, weapon, Target.newFromLocation(150, 50)], // deploy out of effects radius
|
||||
[ship, engine, Target.newFromLocation(110, 50)], // move out of effects radius
|
||||
[ship, engine, Target.newFromLocation(130, 50)], // move in effects radius
|
||||
[ship, weapon, Target.newFromShip(ship)], // recall
|
||||
[ship, weapon, Target.newFromLocation(130, 70)], // deploy in effects radius
|
||||
], [
|
||||
check => {
|
||||
check.equals(ship.active_effects.count(), 0, "active effects");
|
||||
|
@ -56,9 +56,9 @@ module TK.SpaceTac {
|
|||
|
||||
drone.effects = [
|
||||
new DamageEffect(5),
|
||||
new AttributeEffect("skill_quantum", 1)
|
||||
new AttributeEffect("precision", 1)
|
||||
]
|
||||
check.equals(drone.getDescription(), "While deployed:\n• do 5 damage\n• quantum skill +1");
|
||||
check.equals(drone.getDescription(), "While deployed:\n• do 5 damage\n• precision +1");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
module TK.SpaceTac.Specs {
|
||||
testing("Equipment", test => {
|
||||
test.case("generates a full name", check => {
|
||||
let equipment = new Equipment(SlotType.Weapon, "rayofdeath");
|
||||
check.equals(equipment.getFullName(), "rayofdeath Mk1");
|
||||
|
||||
equipment.name = "Ray of Death";
|
||||
check.equals(equipment.getFullName(), "Ray of Death Mk1");
|
||||
|
||||
equipment.quality = EquipmentQuality.LEGENDARY;
|
||||
check.equals(equipment.getFullName(), "Legendary Ray of Death Mk1");
|
||||
});
|
||||
|
||||
test.case("checks capabilities requirements", check => {
|
||||
var equipment = new Equipment();
|
||||
var ship = new Ship();
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), true);
|
||||
|
||||
equipment.requirements["skill_time"] = 2;
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), false);
|
||||
|
||||
TestTools.setAttribute(ship, "skill_time", 1);
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), false);
|
||||
|
||||
TestTools.setAttribute(ship, "skill_time", 2);
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), true);
|
||||
|
||||
TestTools.setAttribute(ship, "skill_time", 3);
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), true);
|
||||
|
||||
// Second requirement
|
||||
equipment.requirements["skill_materials"] = 3;
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), false);
|
||||
|
||||
TestTools.setAttribute(ship, "skill_materials", 4);
|
||||
|
||||
check.equals(equipment.canBeEquipped(ship.attributes), true);
|
||||
});
|
||||
|
||||
test.case("generates a description of the effects", check => {
|
||||
let equipment = new Equipment();
|
||||
check.equals(equipment.getEffectsDescription(), "does nothing");
|
||||
|
||||
let action = new TriggerAction(equipment, [new DamageEffect(50)], 1, 200, 0);
|
||||
equipment.action = action;
|
||||
check.equals(equipment.getEffectsDescription(), "Fire (power 1, range 200km):\n• do 50 damage on target");
|
||||
|
||||
action = new TriggerAction(equipment, [new DamageEffect(50)], 1, 200, 20);
|
||||
equipment.action = action;
|
||||
check.equals(equipment.getEffectsDescription(), "Fire (power 1, range 200km):\n• do 50 damage in 20km radius");
|
||||
|
||||
action = new TriggerAction(equipment, [
|
||||
new DamageEffect(50),
|
||||
new StickyEffect(new AttributeLimitEffect("shield_capacity", 200), 3)
|
||||
], 1, 200, 0);
|
||||
equipment.action = action;
|
||||
check.equals(equipment.getEffectsDescription(), "Fire (power 1, range 200km):\n• do 50 damage on target\n• limit shield capacity to 200 for 3 turns on target");
|
||||
});
|
||||
|
||||
test.case("gets a minimal level, based on skills requirements", check => {
|
||||
let equipment = new Equipment();
|
||||
check.equals(equipment.getMinimumLevel(), 1);
|
||||
|
||||
equipment.requirements["skill_quantum"] = 10;
|
||||
check.equals(equipment.getMinimumLevel(), 1);
|
||||
|
||||
equipment.requirements["skill_time"] = 1;
|
||||
check.equals(equipment.getMinimumLevel(), 2);
|
||||
|
||||
equipment.requirements["skill_gravity"] = 2;
|
||||
check.equals(equipment.getMinimumLevel(), 2);
|
||||
|
||||
equipment.requirements["skill_antimatter"] = 4;
|
||||
check.equals(equipment.getMinimumLevel(), 3);
|
||||
});
|
||||
|
||||
test.case("weighs the price, taking wear into account", check => {
|
||||
let equipment = new Equipment();
|
||||
check.equals(equipment.getPrice(), 0);
|
||||
|
||||
equipment.price = 100;
|
||||
check.equals(equipment.getPrice(), 100);
|
||||
|
||||
equipment.addWear(1);
|
||||
check.equals(equipment.getPrice(), 99);
|
||||
|
||||
equipment.addWear(10);
|
||||
check.equals(equipment.getPrice(), 97);
|
||||
|
||||
equipment.addWear(89);
|
||||
check.equals(equipment.getPrice(), 83);
|
||||
|
||||
equipment.addWear(400);
|
||||
check.equals(equipment.getPrice(), 50);
|
||||
|
||||
equipment.addWear(12500);
|
||||
check.equals(equipment.getPrice(), 3);
|
||||
});
|
||||
|
||||
test.case("builds a full textual description", check => {
|
||||
let equipment = new Equipment();
|
||||
equipment.name = "Super Equipment";
|
||||
equipment.requirements["skill_gravity"] = 2;
|
||||
equipment.effects.push(new AttributeEffect("skill_time", 3));
|
||||
equipment.wear = 50;
|
||||
|
||||
let result = equipment.getFullDescription();
|
||||
check.equals(result, "Second hand\n\nRequires:\n• gravity skill 2\n\nWhen equipped:\n• time skill +3");
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
module TK.SpaceTac {
|
||||
/**
|
||||
* Quality of loot.
|
||||
*/
|
||||
export enum EquipmentQuality {
|
||||
WEAK,
|
||||
COMMON,
|
||||
FINE,
|
||||
PREMIUM,
|
||||
LEGENDARY
|
||||
}
|
||||
|
||||
// Piece of equipment to attach in slots
|
||||
export class Equipment extends RObject {
|
||||
// Type of slot this equipment can fit in
|
||||
slot_type: SlotType | null
|
||||
|
||||
// Actual slot this equipment is attached to
|
||||
attached_to: Slot | null = null
|
||||
|
||||
// Identifiable equipment code (may be used by UI to customize visual effects)
|
||||
code: string
|
||||
|
||||
// Equipment name
|
||||
name: string
|
||||
|
||||
// Equipment generic description
|
||||
description = ""
|
||||
|
||||
// Indicative equipment level
|
||||
level = 1
|
||||
|
||||
// Indicative equipment quality
|
||||
quality = EquipmentQuality.COMMON
|
||||
|
||||
// Base price
|
||||
price = 0
|
||||
|
||||
// Minimum skills to be able to equip this
|
||||
requirements: { [key: string]: number } = {}
|
||||
|
||||
// Permanent effects on the ship that equips this
|
||||
effects: BaseEffect[] = []
|
||||
|
||||
// Action available when equipped
|
||||
action: BaseAction | null = null
|
||||
|
||||
// Equipment wear due to usage in battles (will lower the sell price)
|
||||
wear = 0
|
||||
|
||||
// Cooldown needed by the equipment
|
||||
cooldown = new Cooldown()
|
||||
|
||||
// Basic constructor
|
||||
constructor(slot: SlotType | null = null, code = "equipment") {
|
||||
super();
|
||||
|
||||
this.slot_type = slot;
|
||||
this.code = code;
|
||||
this.name = code;
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
return this.attached_to ? `${this.attached_to.ship.getName()} - ${this.name}` : this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified name (e.g. "Level 4 Strong Ray of Death")
|
||||
*/
|
||||
getFullName(): string {
|
||||
let name = this.name;
|
||||
if (this.quality != EquipmentQuality.COMMON) {
|
||||
name = capitalize(EquipmentQuality[this.quality].toLowerCase()) + " " + name;
|
||||
}
|
||||
return `${name} Mk${this.level}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full textual description for this equipment (without the full name).
|
||||
*/
|
||||
getFullDescription(): string {
|
||||
let requirements: string[] = [];
|
||||
iteritems(this.requirements, (skill, value) => {
|
||||
if (isShipAttribute(skill) && value > 0) {
|
||||
requirements.push(`• ${SHIP_VALUES_NAMES[skill]} ${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;
|
||||
}
|
||||
if (this.cooldown.overheat > 0) {
|
||||
description = `${this.cooldown}\n\n${description}`;
|
||||
}
|
||||
if (this.wear > 0) {
|
||||
description = (this.wear >= 100 ? "Worn" : "Second hand") + "\n\n" + description;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum level at which the requirements in skill may be fulfilled.
|
||||
*
|
||||
* This is informative and is not directly enforced. It will only be enforced by skills requirements.
|
||||
*/
|
||||
getMinimumLevel(): number {
|
||||
let points = sum(values(this.requirements));
|
||||
return ShipLevel.getLevelForPoints(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the equipment price value.
|
||||
*/
|
||||
getPrice(): number {
|
||||
return Math.floor(this.price * 500 / (500 + this.wear));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the equipment can be equipped on a ship with given skills.
|
||||
*
|
||||
* This checks *requirements* against the effective (modified) skills.
|
||||
*
|
||||
* This does not check where the equipment currently is (except if is it already attached and should be detached first).
|
||||
*/
|
||||
canBeEquipped(skills: ShipAttributes, check_unattached = true): boolean {
|
||||
if (check_unattached && this.attached_to) {
|
||||
return false;
|
||||
} else {
|
||||
var able = true;
|
||||
iteritems(this.requirements, (attr, minvalue) => {
|
||||
if (isShipAttribute(attr) && skills[attr].get() < minvalue) {
|
||||
able = false;
|
||||
}
|
||||
});
|
||||
return able;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach from the slot it is attached to
|
||||
*/
|
||||
detach(): void {
|
||||
if (this.attached_to) {
|
||||
this.attached_to.attached = null;
|
||||
this.attached_to = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human readable description of the effects of this equipment
|
||||
*/
|
||||
getEffectsDescription(): string {
|
||||
let parts: string[] = [];
|
||||
|
||||
if (this.effects.length > 0) {
|
||||
parts.push(["When equipped:"].concat(this.effects.map(effect => "• " + effect.getDescription())).join("\n"));
|
||||
}
|
||||
|
||||
if (this.action) {
|
||||
let action_desc = this.action.getEffectsDescription();
|
||||
if (action_desc != "") {
|
||||
parts.push(action_desc);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.length > 0 ? parts.join("\n\n") : "does nothing";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add equipment wear
|
||||
*/
|
||||
addWear(factor: number): void {
|
||||
this.wear += factor;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -164,40 +164,5 @@ module TK.SpaceTac {
|
|||
ship4.setDead();
|
||||
check.equals(fleet.isAlive(), false);
|
||||
});
|
||||
|
||||
test.case("adds cargo in first empty slot", check => {
|
||||
let fleet = new Fleet();
|
||||
let ship1 = fleet.addShip();
|
||||
ship1.cargo_space = 1;
|
||||
let ship2 = fleet.addShip();
|
||||
ship2.cargo_space = 2;
|
||||
|
||||
check.equals(ship1.cargo, []);
|
||||
check.equals(ship2.cargo, []);
|
||||
|
||||
let equipment1 = new Equipment();
|
||||
let result = fleet.addCargo(equipment1);
|
||||
check.equals(result, true);
|
||||
check.equals(ship1.cargo, [equipment1]);
|
||||
check.equals(ship2.cargo, []);
|
||||
|
||||
let equipment2 = new Equipment();
|
||||
result = fleet.addCargo(equipment2);
|
||||
check.equals(result, true);
|
||||
check.equals(ship1.cargo, [equipment1]);
|
||||
check.equals(ship2.cargo, [equipment2]);
|
||||
|
||||
let equipment3 = new Equipment();
|
||||
result = fleet.addCargo(equipment3);
|
||||
check.equals(result, true);
|
||||
check.equals(ship1.cargo, [equipment1]);
|
||||
check.equals(ship2.cargo, [equipment2, equipment3]);
|
||||
|
||||
let equipment4 = new Equipment();
|
||||
result = fleet.addCargo(equipment4);
|
||||
check.equals(result, false);
|
||||
check.equals(ship1.cargo, [equipment1]);
|
||||
check.equals(ship2.cargo, [equipment2, equipment3]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -145,19 +145,5 @@ module TK.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|