From 9c358113db9632aeea69b01d09e04132c5240d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Fri, 17 Mar 2017 01:07:00 +0100 Subject: [PATCH] Added skill upgrading --- README.md | 13 +++- TODO | 3 +- out/assets/images/battle/action-tooltip.png | Bin 1084 -> 1864 bytes out/assets/images/battle/shiplist-enemy.png | Bin 1062 -> 1296 bytes out/assets/images/battle/shiplist-own.png | Bin 1046 -> 1284 bytes out/assets/images/character/skill-upgrade.png | Bin 0 -> 1547 bytes src/common | 2 +- src/core/BattleOutcome.spec.ts | 4 +- src/core/BattleOutcome.ts | 2 +- src/core/Fleet.spec.ts | 6 +- src/core/Fleet.ts | 4 +- src/core/Ship.spec.ts | 23 +++++++ src/core/Ship.ts | 60 ++++++++++++----- src/core/ShipLevel.spec.ts | 43 ++++++++++++ src/core/ShipLevel.ts | 61 ++++++++++++++++++ src/ui/Preload.ts | 1 + src/ui/battle/ShipListItem.ts | 4 ++ src/ui/character/CharacterSheet.ts | 58 +++++++++++------ 18 files changed, 236 insertions(+), 48 deletions(-) create mode 100644 out/assets/images/character/skill-upgrade.png create mode 100644 src/core/ShipLevel.spec.ts create mode 100644 src/core/ShipLevel.ts diff --git a/README.md b/README.md index 87edf2f..5787c54 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,14 @@ After making changes to sources, you need to recompile: ## Ships +### Level and experience + +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). + +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). + ### In-combat values (HSP) In combat, a ship's vitals are represented by the HSP system (Hull-Shield-Power): @@ -55,8 +63,6 @@ or a temporary effect caused by a weapon or a drone). For example, a ship that equips a power generator with "power recovery +3", but has a sticky effect of "power recovery -1" from a previous weapon hit, will have an effective power recovery of 2. -Attributes may also be upgraded permanently during level up. - ### Skills Skills represent a ship's ability to use equipments: @@ -72,7 +78,8 @@ Each equipment has minimal skill requirements to be used. For example, a weapon and "energy >= 3" to be equipped. A ship that does not meet these requirements will not be able to use the equipment. -Like for attributes, skill values are controlled by equipments, effects and level up. +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 diff --git a/TODO b/TODO index 1b2f7fb..e24b0de 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,10 @@ * UI: Use a common component class, and a layer abstraction * Character sheet: allow item moving to another ship * Character sheet: add tooltips (on values, slots and equipments) -* Character sheet: add levelling up (spending of available points) + initial character creation +* Character sheet: add initial character creation * Character sheet: disable interaction during battle (except for loot screen) * Add battle statistics and/or critics in outcome dialog +* Add battle experience and feedback on level up * Ensure that tweens and particle emitters get destroyed once animation is done (or view changes) * Highlight ships that will be included as target of current action * Controls: Do not focus on ship while targetting for area effects (dissociate hover and target) diff --git a/out/assets/images/battle/action-tooltip.png b/out/assets/images/battle/action-tooltip.png index 08438c0522cde9d5fea68809adc5b825d24e1c26..f8d45a121e833bb303b5cce771b1c471c2ff4a48 100644 GIT binary patch literal 1864 zcmeHIdrVtp6hG~yqdZFIn4|-wtxm%dQ$RN!;n9tUlmt-Oj4XhZ%!+J*%Yd!4m(n{( zV2)Sg5@4e#l9(+%7&9saNugK-7b3S21>6OS$YZRQma+0EYp-|FKa78x@ZY}VL?JLi1#R%R;4JJ1^dIBAmPEC43GWU$!>=;@d-{pp2Ad1BfbHa)7??_Z_&UZs+= z<+T6wQ)48Cc2v@bM=DaXD^8;yRLBd;WFVKzMMWhS%Fh*)%0y^cVaIx0Ai#ki(vnY| zsnlBM^d;m>;K{eM+F#5F5fEM5>Q-2*fi>o*uigF`H+FqIv#1Xs-t##an)8it;jnq7 zrF&PY7x{yxOks-xctUvrunz$oJPII?0Gu@Cr&8a`Hd0xIQN$)T!~*x2pAT;4R2SH_ zTXimF_Xrr;L;P^7bSUZp%dmN4RUyxtN>ZNg)!7zLvC+x zAq%tf!GI;X6$t4yg-{H79tZdyOA?*Eg**tL9VFt?MDEl#M~30FBE zueH<-2`c%daIgt4x_OH)u1+@(s>nG$9ufSg5bqlsYvJFvIFuuMi!Z2lAIY3fhoz!F z*X@6PodrDO7w+fn^Cu{MPmK72cX)5T*hh-Zb#4yG`JV7cWhkuAZ4kS~)06j^jmxGt zk>f{4%WPSMH3n8MNg&$EZ*X|<%pHi-CNY_KEhkvK$$~cUz4leK575z2W-fZl!R{l%OGl#zgx=%PLLt#ac@K9wRSyA*(L2b;EFV2W~ zg;v0wdc>gNO%~^MhZ)*wA(O)kuL5YtXjZ3kCaeMFQ~uIiuG!{IUa;%bFX%a)tR*EZWY~ZmECBS?M@Gp zJ&JtG@L=mv#Rx!%Pz&Eke zFdOSMV{;~TH72~<$C3u43CP_hoRnR$TI$APW(S1!`2^6C#x`(2P6xs=f8SNYhY5>D zjO20^wTs&I?hab>lSW2(1{HFMe5o2)(ta6anDO(%zs=|PwIR9&A=h-N5}sVUf&lTm vhKn#^B89Ft`$Xoy3)-_M_BC(*Kh*96!3|E&dqIXKy6Zt&N@nt%L}|@mzpoFC literal 1084 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83IXKvWl;&X{kOXIeM`STj{yGRVI%&+V01C2~ zc>21sKV;T0g7@3pc^6X z#^qof${m0jAyNn<5Mm8LSq!hEn?#~=)bJ%}I;wJn@9+fBpX*gmHyXNWER85FT5GDz zaOcjXV!N{p4Cf!Q!aPfq$55@Ng-h{x0w{&>tO76|h)R|eMkUA+^ynqpV!8y-zRzqw Y4@w@t^ZjfxFiaRcUHx3vIVCg!0AAZ~D*ylh diff --git a/out/assets/images/battle/shiplist-enemy.png b/out/assets/images/battle/shiplist-enemy.png index 0fb195bc7ac5dcc8444d2ded3cb86ea16b4b504c..17d5b77e3f6be7cbe316931ea8c2f28709f03a7b 100644 GIT binary patch delta 1176 zcmV;J1ZVrE2#^YpQhx&x4?8AXDg{*l00f;$L_t(|+U?y>NE}xH$MN4=_sypMX;GuL z1e!}oVzro)IW!kxL8uMVhF6a%^BrnXp>rj)b@+EOUGtM0CU zFed)Prnsy7cGTUOab6FWO*F=4Y1DQ3eNVgcoPqb*nQ<1GCx3dR)L=)J7NVuuvbqm* zA0AF~?nTrb%$t5C1fqBDa7GTH!u$8(8y)45nybBc?b?T0Yf01`s=T4$t3)F4mr|-L z9*=((jYi)-ywmSK#bPm7mPLt7juMXt<>lr7HLL_cvk;P3Al@XN@l@XN@m8guU zjHryLjHpCqL}f%}dx{5xu-Dg#noG^!zKy|)7ctt}ilqk+V6Cna^(3YX3U7_6+spN)-JxOWf9m6bQLKQE{J@Y`r@#g^X>V|*M&AOQK~384S|&rjFI+VC*e zhKDhA>wgvsE?huGV*|oKtAl544Ms2sBOHczW(LA_pMLI!=iKtl(TwI(67N!X!5`ixefQ&?d?d^Sm-#U91W%c!_Xlz9B z<;#E&M9tdkZDAo&wY5mq)rrVuu)QiQokQPKHt@7D)rNs zfKAkF1vU&=)zz@7t9O7WC*cbQAwwaEt*!JpD;C2_R~G>EotAR72k0Z}Ndc~@fmKrj zI71HP^fZiM5Wa~C2-AE%4mb_q3!KPd2F<_?EW!lCI_uK1(VnUI)5^v5|t5^5tR{@5tR{@sEnwLsEnxW)#WWM?Rw9r zh@Qk;7v9H@cNSV08$(_!M%0s-ux-exDHvm8FhZeS&-vX`>Oj=Cwl=sgti-@xWAYDJ zupr}YZEo&7uz6zQrAMysX%j;Hs4Zy}s(>1kXkQ0000(;V~w00Xm0L_t(|+U?!HYa3@A$MMhibl*o!<)LX( zFNF>rGSmhy#k(L&yOk1U($G@qQaqWi?*HJikhKt9sOprY1QH6NrHd`uK1s20no=2x zORP9WvUERmPX`ypa%C4&|B%e*Jz2-Y9Xv1Y&KCH2#8b6kXMdIz$=wLU!f9T^;WS^o zhLX9}i`$njy?qkb zb2;UwU+d;gw69->Sy_Q8m7vd_0k-$ghA9A909hxF=RMV`6)9MeZ*oDfM${oLxpD=a zY!;nt7S4+oFy%5#r2=<-9g-ybhrTD)&WW{iJz#6^=6|ASk*LEAeED(@Sei|kY8B~H z30kcJ-R{4EfjLk0R{-B9))sEW@lOI+q7Li(c5Dp6^fZF$X-KODQ>h?bD#5MQAaT5Z z=(_-B9Fdtjs=s?cQLMfDW*q;R1!AHO@h?<8;4p-)ufr@Y!K|!6I-UJP-vKaVMP?eR z&pqHi9Dk><9Rxpo4n#y9R$yZgPEI16oZJJh)sQZip}r4kw_l9|t^&v-N%B|sfES25 zD!@}y2&bliIEJpR!IaBLudYIx&4a+75_QyaT|^TTh$be`N)qVC228n(*B(Fa{h7os zA4WOHL3HgJTDe^RVb072wB{5Ll@XN@l@XN@l?$khsEnwLsEnvYWs{EsI)AdmOKooU zeda2nqgax_efo57qlM*Vq#6yPj$#r;&?iq|mX~3Czi*x20|iH-=JRST;Ja=L>^gd3pR?rI}%k@sdU!w$DJK$r2^yo&{_@X-yGo;IPl#5 zvv&ULLGa*1AmHP7m>{>ep?`xnB05-a8U9|3M|yD)sjaP} zMjv~_A)=nuyEcyDZfwB#K1`_u=jF@8hIRq`0-zwSd*|bJd-K@W@>DIS{M>5_a33N? zCk*d>1nm5ulkrYHzy1_p0ZCH)tJ{6}3D7yI6BG4p`whUtVcU6~R2F{&vbSAcYu={M P00000NkvXXu0mjfMySHe diff --git a/out/assets/images/battle/shiplist-own.png b/out/assets/images/battle/shiplist-own.png index 6cf301eef8858ae974bd7fa22bd523511ed4d6aa..58a18d3c1225995a5309b41ef4120d673a7f4f61 100644 GIT binary patch delta 1218 zcmbQn(ZV$$sGeDn-&$kOB#j^j29}AQE{-7;x8C0M&k+e_IR5ed?J}>+tU^*j$tsr= zM5fv|3ah+goiN2=F-OO&2M3wwUEyB#FyWH)S2cFwRfaD9S&KDw10UDxN(sN%b~ktX zS_P%GEg8En++^Hly!o>YhqR{dEf4SAKWqJWpS?ZvNv%w?CaKFD3-X?JWhfqKTPgokB{^Fiu}tD=U##puUA{mzv#sv9LjLqq?Ytr5DB+;j7>#km_VUSwDo z+$o&XudSuUwp?`U(xt5j57s++`Yx=nIoHU{zUKGPT2abF2Vl7tID`zrFXaGnC-s* zc~5mql*|Fq1AN)J!6L5vcdx5HRC@PUj@j=C0o{v*9tbYD7&W^rc6<1Wqm^=E^;v=O zVVPVltDsOTL=_4bY60P@!qUGp9~8aY6wtl$aOdGGGPMo1xw|X$|LPsS-qva*-aY$= z@B3zr2d6*Be>i2Xzwy(HqE~&n&)231i|^qsPpMxz>(k5kGk+B4Xx{fSdZG5P{6lty zSoPoeyvovrNh{_GJJ-LxvVH%l=I*dhM?TH9*_vc-+MU&6H9I&GebF z@bvp7>K-494IErTB(B$IHvF61yQgl`MuwQ^y}hNOuUoBVukH!mwDM1W!uhH%Y_=+Y z_%7AySAG&^(vOYs%`MgS*SmkRt=c@vz-jK~25g(in<~9CY5fJ^`XwGeWWW5( zbUJis$Nf9SCaV1xrq~>s^waysz8v_x{cPdw@S^dIYjBldnH*T2Ua^G?L zeb;l(xE{CdS;$n7TB+Kvf7i=Xx%8)N&9QD4r@2d~&NRAaksbMX&C&HAJlXe_u+Clb zN9FpPt%r8_=pANfe$TvRT8hLofwfL^>y;0@K6P#Lx*Xd?*Aosm{Fl#Hcv=29^8K>+ z$p^Td<|a>_6%=Nde0 z-*!uMy0ut|b921-e7S@@cFCV7KYsuDw|V33MelBJ@7L%uSsZ*qqWJi>(yMVx?O!O1 zs^sr{`s>HS1J@;HeJ*&jZAIT>kIc5+BC(6iUnq*I)W77u)M7R#=Y_!s&9-MXtn)t` zB?HrX;Ngn~p8v103;JG&WIk?a(r#I|q>*!a;KF;YI@1n*uvw|#* YEqQ-&QsVv+AqF7uboFyt=akR{0NgcJDF6Tf delta 978 zcmZqSn#M69sGgCNS4667!_I>Y49tr?T^vIyZoR#GH#<6&;rPe*lP{+%z1ceLrnml{ zfCBv-g`DlLU2fPvkbdIE^1domhk4;%qut_(3USqfSJq6{Y+A&8wEO8^-{|n=I>O6R zBK0@v*3O&!xgs(9wvHsr?@3ecf7i0KnRDRtLmPF@ImJE`jxGLHzxn#@=_lSiWj|ta zyj_2x+#xr!cnKc){`iYMZye{F*(;{!zw!um3cWk+ex9im@0zW%me%!6is^$gt zHf{5jEK|>~fkrb^s;Z(+uNPV+v?^dK9Ja1%fk3SgRT#X&d{QDVu`j))s^inV@6Gkf ze?*t;lv`3coxd_~yM40DcGwtAr;o%A2afbK(Po=ESs`4L}2edCRpPc{s z)r;&Yv7LL)eypyL-e0;gvElii#?Oui+|_(VPqY}?CAWz#-h>EZ!A=E|IeEDORYPvanISnt=(HP z?HS)Bnt2r8J`gTp^pM9su_``X=L++?6_%%N9#BCvMAlZ}allQTu{h zZrKMP*6BNwFQqhQKeVzp*j~IKfc*{kRkaOYr%f%G2{d+-Yt+82wJ$!)Eo)f$fTQ$M zO|RnRx@qel=4TU>`YK;w@7Sbm+t2q_V)^Ru?1BGIW}8mK5Mm7 zIj~$f-D0kVvxV3uA-&)Ym8Z73TsF6Eyf64_f@zYQg`EAtk3TpUr5stc+x6hHog4o> zuD753rT>zExbm^?kJBrZe})||E9KaT96|M%F%+^;3ktbHIpJ`H7Zf@HQv(!=0;3xQ zU){XBVcX-q@06@sTwVS=3)9@Gq#FICt|WfN;TeZFrmUV4GqFpz{ND5O>ev3RzDt8# z0+&d>RZc#$?(OUPiSTcf7eaRXhY}>-(-(TQ2|4&c|6gpNliHbo+`OMy#{bb)#OJZsGm&h7>ci#TqBe$el z{&m*|A-TVIx4E+0#SH2DV&(OOx V+}NCd;SmNP@O1TaS?83{1OREN(yjmi diff --git a/out/assets/images/character/skill-upgrade.png b/out/assets/images/character/skill-upgrade.png new file mode 100644 index 0000000000000000000000000000000000000000..b2998921c90d3cc4345b88aa7af76941efcf660f GIT binary patch literal 1547 zcmV+m2K4!fP)lb!vR0O3n zRYY94aZwdTK}B6ybx|o46}6*|)6NW9b!scxI%y`8ypMa2i@bE2G&4;yGqpVs0(p=3 z?(d$D|2a2u^|ogeNPsFWQz{4*fq5rXM43nQI4W}r$3*zULynmt``cu>9C03)q#%Qu z1r7r`^B+$y5xSOTy_^4NH_aeV4uPsK|VP& zHu~#@3eskZ{3NO$qr5%6YsbWS0U`oM1Q0~hg{qVw*61Jsb9_n(fo$+xMLl5Z`!|@;NTqP~yQ`iWIVfbINB26ZX!`b7G-J zBaZv=N+-}qR^8*X@My3edCtPngS1R0-;h6-FftZXV+HWwY2jN~Z>0lw!Ut;89q zC~$`nGja8{r=P#_ftQ1I0cpWxJddGlHcj}Zib)_J#+WEfShCV z72de!;iqrw8ARl4pXR=I3!%3AQnBpz8PGCLR6t*lq30C)K-wH&@#Qfn_w-cFDp<5SEB3Rm4eySlOB}A5Hv*BF8mW><8cpkMV!a9eZwn(Q+Y%R4gqFQfIZG=tXwEerq zsJU+$k1Q?l&KpWxTnfngKE|9`iHs34o}rL!U}R|#TSgWc-s%&@c@h`2c|%c9h@GV# z#q61?F?pmyVl9d7e`Ha(d0CHS{bv7k{T7dn`LHc)da=M%*qjIQXTSI7qd9*0dy!fg z4x0FvU#R7`oAh^?Q&owQglZ#XVlL=0w6ct3|3PSmM(9SUgg#N6oRR3Ap*1&OqaMXr zH}E9YC`i`wGlFRqQW01=2>VodM>{MwMSN7U&bTgXu>4Ut|*8> z#wX|d)WY`Tt=2}Itk8^N8jz&rR@XM3zV}3KoE~e3SQ8LU zt6MFK+B(-poDjwqyEdMQL0`xe-Az+FM`wCg_I@C&Q^L5ZHGfr$6tV{Alw*}T3t_A5 zt^!2(0Oy{V9xos4yG7~8oGSG==AY^UyS{L32XrUv4dPTk`eqTnaLVZH&fZQO{naM8 z!x6>_>XdpEcRe!gBeWur4;3As*}3DZRX2@;z(iJwtR3_a`vBMoW9NW~er(jeGc{KJX 0.9) { // Salvage a supposedly transported item - var transported = this.generateLootItem(random, ship.level); + var transported = this.generateLootItem(random, ship.level.get()); if (transported) { this.loot.push(transported); } diff --git a/src/core/Fleet.spec.ts b/src/core/Fleet.spec.ts index e4dad0f..8d43906 100644 --- a/src/core/Fleet.spec.ts +++ b/src/core/Fleet.spec.ts @@ -8,9 +8,9 @@ module TS.SpaceTac { fleet.addShip(new Ship()); fleet.addShip(new Ship()); - fleet.ships[0].level = 2; - fleet.ships[1].level = 4; - fleet.ships[2].level = 7; + fleet.ships[0].level.forceLevel(2); + fleet.ships[1].level.forceLevel(4); + fleet.ships[2].level.forceLevel(7); expect(fleet.getLevel()).toEqual(4); }); diff --git a/src/core/Fleet.ts b/src/core/Fleet.ts index 34e6a97..ee369f1 100644 --- a/src/core/Fleet.ts +++ b/src/core/Fleet.ts @@ -71,10 +71,10 @@ module TS.SpaceTac { var sum = 0; this.ships.forEach((ship: Ship) => { - sum += ship.level; + sum += ship.level.get(); }); var avg = sum / this.ships.length; - return Math.round(avg); + return Math.floor(avg); } // Check if the fleet still has living ships diff --git a/src/core/Ship.spec.ts b/src/core/Ship.spec.ts index 5f016c7..5b18aac 100644 --- a/src/core/Ship.spec.ts +++ b/src/core/Ship.spec.ts @@ -400,5 +400,28 @@ module TS.SpaceTac.Specs { expect(ship.listEquipment()).toEqual([]); expect(ship.cargo).toEqual([equipment]); }); + + it("allow skills upgrading from current level", function () { + let ship = new Ship(); + expect(ship.level.get()).toBe(1); + expect(ship.getAvailableUpgradePoints()).toBe(10); + + ship.level.forceLevel(2); + expect(ship.level.get()).toBe(2); + expect(ship.getAvailableUpgradePoints()).toBe(15); + + expect(ship.getAttribute("skill_energy")).toBe(0); + ship.upgradeSkill("skill_energy"); + expect(ship.getAttribute("skill_energy")).toBe(1); + + range(50).forEach(() => ship.upgradeSkill("skill_gravity")); + expect(ship.getAttribute("skill_energy")).toBe(1); + expect(ship.getAttribute("skill_gravity")).toBe(14); + expect(ship.getAvailableUpgradePoints()).toBe(0); + + ship.updateAttributes(); + expect(ship.getAttribute("skill_energy")).toBe(1); + expect(ship.getAttribute("skill_gravity")).toBe(14); + }); }); } diff --git a/src/core/Ship.ts b/src/core/Ship.ts index 094479e..ffa7759 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -3,10 +3,23 @@ module TS.SpaceTac { + /** + * Set of upgradable skills for a ship + */ + export class ShipSkills { + // Skills + skill_material = new ShipAttribute("material skill") + skill_energy = new ShipAttribute("energy skill") + skill_electronics = new ShipAttribute("electronics skill") + skill_human = new ShipAttribute("human skill") + skill_time = new ShipAttribute("time skill") + skill_gravity = new ShipAttribute("gravity skill") + } + /** * Set of ShipAttribute for a ship */ - export class ShipAttributes { + export class ShipAttributes extends ShipSkills { // Attribute controlling the play order initiative = new ShipAttribute("initiative") // Maximal hull value @@ -19,13 +32,6 @@ module TS.SpaceTac { power_initial = new ShipAttribute("initial power") // Power value recovered each turn power_recovery = new ShipAttribute("power recovery") - // Skills - skill_material = new ShipAttribute("material skill") - skill_energy = new ShipAttribute("energy skill") - skill_electronics = new ShipAttribute("electronics skill") - skill_human = new ShipAttribute("human skill") - skill_time = new ShipAttribute("time skill") - skill_gravity = new ShipAttribute("gravity skill") } /** @@ -40,6 +46,7 @@ module TS.SpaceTac { /** * Static attributes and values object for name queries */ + export const SHIP_SKILLS = new ShipSkills(); export const SHIP_ATTRIBUTES = new ShipAttributes(); export const SHIP_VALUES = new ShipValues(); @@ -51,7 +58,8 @@ module TS.SpaceTac { fleet: Fleet // Level of this ship - level: number + level = new ShipLevel() + skills = new ShipSkills() // Name of the ship name: string @@ -91,13 +99,9 @@ module TS.SpaceTac { // Priority in play_order play_priority = 0; - // Upgrade points available - upgrade_points = 0; - // Create a new ship inside a fleet constructor(fleet: Fleet | null = null, name = "Ship") { this.fleet = fleet || new Fleet(); - this.level = 1; this.name = name; this.model = "default"; this.alive = true; @@ -172,6 +176,24 @@ module TS.SpaceTac { return actions; } + /** + * Get the number of upgrade points available to improve skills + */ + getAvailableUpgradePoints(): number { + let used = keys(SHIP_SKILLS).map(skill => this.skills[skill].get()).reduce((a, b) => a + b, 0); + return this.level.getSkillPoints() - used; + } + + /** + * Try to upgrade a skill by 1 point + */ + upgradeSkill(skill: keyof ShipSkills) { + if (this.getAvailableUpgradePoints() > 0) { + this.skills[skill].add(1); + this.updateAttributes(); + } + } + // Add an event to the battle log, if any addBattleEvent(event: BaseLogEvent): void { var battle = this.getBattle(); @@ -550,8 +572,16 @@ module TS.SpaceTac { // Update attributes, taking into account attached equipment and active effects updateAttributes(): void { if (this.alive) { - // Sum all attribute effects var new_attrs = new ShipAttributes(); + + // TODO better typing for iteritems + + // Apply base skills + iteritems(this.skills, (key, skill: ShipAttribute) => { + new_attrs[key].add(skill.get()); + }); + + // Sum all attribute effects this.collectEffects("attr").forEach((effect: AttributeEffect) => { new_attrs[effect.attrcode].add(effect.value); }); @@ -561,7 +591,7 @@ module TS.SpaceTac { new_attrs[effect.attrcode].setMaximal(effect.value); }); - // TODO better typing + // Set final attributes iteritems(new_attrs, (key, value) => { this.setAttribute(key, (value).get()); }); diff --git a/src/core/ShipLevel.spec.ts b/src/core/ShipLevel.spec.ts new file mode 100644 index 0000000..7c8cf29 --- /dev/null +++ b/src/core/ShipLevel.spec.ts @@ -0,0 +1,43 @@ +module TS.SpaceTac.Specs { + describe("ShipLevel", () => { + it("level up from experience points", () => { + let level = new ShipLevel(); + expect(level.get()).toEqual(1); + expect(level.getNextGoal()).toEqual(100); + expect(level.getSkillPoints()).toEqual(10); + + level.addExperience(60); // 60 + expect(level.get()).toEqual(1); + expect(level.checkLevelUp()).toBe(false); + + level.addExperience(70); // 130 + expect(level.get()).toEqual(1); + expect(level.checkLevelUp()).toBe(true); + expect(level.get()).toEqual(2); + expect(level.getNextGoal()).toEqual(300); + expect(level.getSkillPoints()).toEqual(15); + + level.addExperience(200); // 330 + expect(level.get()).toEqual(2); + expect(level.checkLevelUp()).toBe(true); + expect(level.get()).toEqual(3); + expect(level.getNextGoal()).toEqual(600); + expect(level.getSkillPoints()).toEqual(20); + + level.addExperience(320); // 650 + expect(level.get()).toEqual(3); + expect(level.checkLevelUp()).toBe(true); + expect(level.get()).toEqual(4); + expect(level.getNextGoal()).toEqual(1000); + expect(level.getSkillPoints()).toEqual(25); + }); + + it("forces a given level", () => { + let level = new ShipLevel(); + expect(level.get()).toEqual(1); + + level.forceLevel(10); + expect(level.get()).toEqual(10); + }); + }); +} diff --git a/src/core/ShipLevel.ts b/src/core/ShipLevel.ts new file mode 100644 index 0000000..925c8f5 --- /dev/null +++ b/src/core/ShipLevel.ts @@ -0,0 +1,61 @@ +module TS.SpaceTac { + /** + * Level and experience system for a ship. + */ + export class ShipLevel { + private level = 1; + private experience = 0; + + /** + * Get current level + */ + get(): number { + return this.level; + } + + /** + * Get the next experience goal to reach, to gain one level + */ + getNextGoal(): number { + return isum(imap(irange(this.level), i => (i + 1) * 100)); + } + + /** + * Force experience gain, to reach a given level + */ + forceLevel(level: number) { + while (this.level < level) { + this.addExperience(this.getNextGoal()); + this.checkLevelUp(); + } + } + + /** + * Check for level-up + * + * Returns true if level changed + */ + checkLevelUp(): boolean { + let changed = false; + while (this.experience > this.getNextGoal()) { + this.level++; + changed = true; + } + return changed; + } + + /** + * Add experience points + */ + addExperience(points: number) { + this.experience += points; + } + + /** + * Get skill points given by current level + */ + getSkillPoints(): number { + return 5 + 5 * this.level; + } + } +} diff --git a/src/ui/Preload.ts b/src/ui/Preload.ts index a0adb87..2270cff 100644 --- a/src/ui/Preload.ts +++ b/src/ui/Preload.ts @@ -93,6 +93,7 @@ module TS.SpaceTac.UI { this.loadImage("character/close.png"); this.loadImage("character/ship.png"); this.loadImage("character/ship-selected.png"); + this.loadImage("character/skill-upgrade.png"); this.loadImage("character/cargo-slot.png"); this.loadImage("character/equipment-slot.png"); this.loadImage("character/slot-power.png"); diff --git a/src/ui/battle/ShipListItem.ts b/src/ui/battle/ShipListItem.ts index cd09520..cedd720 100644 --- a/src/ui/battle/ShipListItem.ts +++ b/src/ui/battle/ShipListItem.ts @@ -58,6 +58,10 @@ module TS.SpaceTac.UI { this.updateAttributes(); this.updateEffects(); + let level = new Phaser.Text(this.game, 103, 22, `${ship.level.get()}`, { align: "center", font: "bold 10pt Arial", fill: "#000000" }); + level.anchor.set(0.5, 0.5); + this.addChild(level); + Tools.setHoverClick(this, () => list.battleview.cursorOnShip(ship), () => list.battleview.cursorOffShip(ship), () => list.battleview.cursorClicked()); } diff --git a/src/ui/character/CharacterSheet.ts b/src/ui/character/CharacterSheet.ts index 7d28193..0093c9a 100644 --- a/src/ui/character/CharacterSheet.ts +++ b/src/ui/character/CharacterSheet.ts @@ -27,8 +27,9 @@ module TS.SpaceTac.UI { // Ship level ship_level: Phaser.Text; - // Ship upgrade points - ship_upgrades: Phaser.Text; + // Ship skill upgrade + ship_upgrade_points: Phaser.Text; + ship_upgrades: Phaser.Group; // Ship slots ship_slots: Phaser.Group; @@ -75,8 +76,11 @@ module TS.SpaceTac.UI { this.ship_level.anchor.set(0.5, 0.5); this.addChild(this.ship_level); - this.ship_upgrades = new Phaser.Text(this.game, 1066, 1054, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" }); - this.ship_upgrades.anchor.set(0.5, 0.5); + this.ship_upgrade_points = new Phaser.Text(this.game, 1066, 1054, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" }); + this.ship_upgrade_points.anchor.set(0.5, 0.5); + this.addChild(this.ship_upgrade_points); + + this.ship_upgrades = new Phaser.Group(this.game); this.addChild(this.ship_upgrades); this.ship_slots = new Phaser.Group(this.game); @@ -106,29 +110,40 @@ module TS.SpaceTac.UI { let x1 = 664; let x2 = 1066; let y = 662; - this.addAttribute(SHIP_ATTRIBUTES.initiative, x1, y); - this.addAttribute(SHIP_ATTRIBUTES.hull_capacity, x1, y + 64); - this.addAttribute(SHIP_ATTRIBUTES.shield_capacity, x1, y + 128); - this.addAttribute(SHIP_ATTRIBUTES.power_capacity, x1, y + 192); - this.addAttribute(SHIP_ATTRIBUTES.power_initial, x1, y + 256); - this.addAttribute(SHIP_ATTRIBUTES.power_recovery, x1, y + 320); - this.addAttribute(SHIP_ATTRIBUTES.skill_material, x2, y); - this.addAttribute(SHIP_ATTRIBUTES.skill_electronics, x2, y + 64); - this.addAttribute(SHIP_ATTRIBUTES.skill_energy, x2, y + 128); - this.addAttribute(SHIP_ATTRIBUTES.skill_human, x2, y + 192); - this.addAttribute(SHIP_ATTRIBUTES.skill_gravity, x2, y + 256); - this.addAttribute(SHIP_ATTRIBUTES.skill_time, x2, y + 320); + this.addAttribute("initiative", x1, y); + this.addAttribute("hull_capacity", x1, y + 64); + this.addAttribute("shield_capacity", x1, y + 128); + this.addAttribute("power_capacity", x1, y + 192); + this.addAttribute("power_initial", x1, y + 256); + this.addAttribute("power_recovery", x1, y + 320); + this.addAttribute("skill_material", x2, y); + this.addAttribute("skill_electronics", x2, y + 64); + this.addAttribute("skill_energy", x2, y + 128); + this.addAttribute("skill_human", x2, y + 192); + this.addAttribute("skill_gravity", x2, y + 256); + this.addAttribute("skill_time", x2, y + 320); } /** * Add an attribute display */ - private addAttribute(attribute: ShipAttribute, x: number, y: number) { + private addAttribute(attribute: keyof ShipAttributes, x: number, y: number) { let text = new Phaser.Text(this.game, x, y, "", { align: "center", font: "18pt Arial", fill: "#FFFFFF" }); text.anchor.set(0.5, 0.5); this.addChild(text); - this.attributes[attribute.name] = text; + this.attributes[SHIP_ATTRIBUTES[attribute].name] = text; + + if (SHIP_SKILLS[attribute]) { + let button = new Phaser.Button(this.game, x + 54, y - 4, "character-skill-upgrade", () => { + this.ship.upgradeSkill(attribute); + this.refresh(); + }); + button.anchor.set(0.5, 0.5); + this.ship_upgrades.add(button); + + this.view.tooltip.bindStaticText(button, `Spend one point to upgrade ${SHIP_ATTRIBUTES[attribute].name}`); + } } /** @@ -172,9 +187,12 @@ module TS.SpaceTac.UI { this.equipments.removeAll(true); + let upgrade_points = ship.getAvailableUpgradePoints(); + this.ship_name.setText(ship.name); - this.ship_level.setText(ship.level.toString()); - this.ship_upgrades.setText(ship.upgrade_points.toString()); + this.ship_level.setText(ship.level.get().toString()); + this.ship_upgrade_points.setText(upgrade_points.toString()); + this.ship_upgrades.visible = upgrade_points > 0; iteritems(ship.attributes, (key, value: ShipAttribute) => { let text = this.attributes[value.name];