1
0
Fork 0

New ship upgrade system, replacing equipments

This commit is contained in:
Michaël Lemaire 2018-02-08 16:16:03 +01:00
parent 32b852e2cd
commit 2f52fd0031
245 changed files with 5376 additions and 7571 deletions

View File

@ -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
View File

@ -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
-------

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
graphics/ui/pics/sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -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

View File

@ -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 => {

View File

@ -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);

View File

@ -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 });
})
})
}

View File

@ -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);
}
}
}
}
}

View File

@ -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());

View File

@ -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;
}
}
}
}

View File

@ -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] });
})
})
}

View File

@ -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);
}
}
}

View File

@ -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");
});
});
}

View File

@ -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");
});
});
}

View File

@ -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;
}
}
}

View File

@ -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]);
});
});
}

View File

@ -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;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More