nml-0.2.4/0000755000061700006170000000000012036626604013034 5ustar abuildabuild00000000000000nml-0.2.4/buildout.cfg0000644000061700006170000000061212036626441015342 0ustar abuildabuild00000000000000[buildout] eggs = PIL ply nml parts = nml regression_test versions = versions develop = . find-links = http://dist.plone.org/thirdparty/ http://pypi.python.org/simple [versions] PIL = 1.1.7 ply = 3.4 [nml] recipe = zc.recipe.egg:scripts eggs = ${buildout:eggs} scripts = nmlc [regression_test] recipe = plone.recipe.command command = cd regression && make NMLC="../bin/nmlc" nml-0.2.4/regression/0000755000061700006170000000000012036626604015214 5ustar abuildabuild00000000000000nml-0.2.4/regression/012_basecost.nml0000644000061700006170000000047712036626441020120 0ustar abuildabuild00000000000000grf { grfid: "NML\12"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } basecost { 1: param[2]; PR_RUNNING: -2; PR_BUILD_VEHICLE: param[1] + 1; PR_BUILD_WAYPOINT_RAIL: 14 - 1; param[param[11]]: param[param[11]+1]; } nml-0.2.4/regression/010_liveryoverride.nml0000644000061700006170000000471512036626442021365 0ustar abuildabuild00000000000000grf { grfid: "test"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* * Turbo train engine (arctic) * Livery override for passenger wagon * Graphics by DanMacK, adopted from OpenGFX+ for testing purposes */ spriteset(turbotrain_engine_set, "opengfx_trains_start.pcx") { [142,112, 8,22, -3,-10] [158,112, 21,15, -14, -7] [190,112, 31,12, -16, -8] [238,112, 21,16, -6, -7] [270,112, 8,24, -3,-10] [286,112, 21,16, -15, -6] [318,112, 32,12, -16, -8] [366,112, 21,15, -6, -7] } spritegroup turbotrain_engine_group { loading: turbotrain_engine_set; loaded: turbotrain_engine_set; } spriteset(normal_passenger_set, "arctic_railwagons.pcx") { [ 0, 0, 8,24, -3,-12] [ 16, 0, 22,17, -14, -9] [ 48, 0, 32,12, -16, -8] [ 96, 0, 22,17, -6, -9] [ 0, 0, 8,24, -3,-12] [ 16, 0, 22,17, -14, -9] [ 48, 0, 32,12, -16, -8] [ 96, 0, 22,17, -6, -9] } spritegroup normal_passenger_group { loading: normal_passenger_set; loaded: normal_passenger_set; } spriteset(turbotrain_passenger_set, "opengfx_trains_start.pcx") { [142,139, 8,21, -3,-10] [158,139, 20,15, -13, -7] [190,139, 28,10, -12, -6] [238,139, 20,16, -6, -7] [270,139, 8,21, -3,-10] [286,139, 20,15, -15, -6] [318,139, 28,10, -16, -6] [366,139, 20,16, -6, -7] } spritegroup turbotrain_passenger_group { loading: turbotrain_passenger_set; loaded: turbotrain_passenger_set; } // Turbotrain engine: item(FEAT_TRAINS, turbotrain, 20) { property { sprite_id: SPRITE_ID_NEW_TRAIN; // We have our own sprites misc_flags: bitmask(TRAIN_FLAG_MU); // We use special sprites for passenger and mail wagons } graphics { turbotrain_engine_group; } livery_override(passenger_wagon) { turbotrain_passenger_group; } } item(FEAT_TRAINS, passenger_wagon, 27) { property { sprite_id: SPRITE_ID_NEW_TRAIN; // We have our own sprites misc_flags: bitmask(TRAIN_FLAG_MU); // We use special sprites for passenger and mail wagons refittable_cargo_classes: bitmask(CC_PASSENGERS); // Allow passengers (and tourists) non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos } graphics { normal_passenger_group; } }nml-0.2.4/regression/003_assignment.nml0000644000061700006170000000051012036626441020451 0ustar abuildabuild00000000000000param[0] = 3; param[1] = 2 + 2; param[2] = param[0] + param[1]; param[3] = param[0] * 3; param[4] = bitmask(0, 1, 3); // 00001011 = 11 = 0x0B param[5] = param[4] & 3; param[6] = param[1] + param[2] + param[3]; param[7] = param[param[3]]; param[param[0]] = 5; param[param[0]] = param[param[3]]; //param[5] = min(3, param[3], 4);nml-0.2.4/regression/015_basic_object.nml0000644000061700006170000000207712036626441020725 0ustar abuildabuild00000000000000grf { grfid: "NML\15"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } spritelayout obj_basic_tile { ground { sprite : 1420; } building { sprite : 2632; recolour_mode : RECOLOUR_REMAP; palette : 0x307; xextent : 16; yextent : 16; zextent : 30; } } item (FEAT_OBJECTS, obj_basic) { property { class : "MISC"; classname : string(STR_OBJ_MISC_CLASS); name : string(STR_OBJ_BASIC); climates_available : ALL_CLIMATES & ~bitmask(CLIMATE_TOYLAND); size : [ 1, 1 ]; introduction_date : date(1900, 1, 1); end_of_life_date : date(1930, 1, 1); build_cost_multiplier : 1; remove_cost_multiplier : 1; object_flags : bitmask(OBJ_FLAG_ALLOW_BRIDGE); height : 4; } graphics { obj_basic_tile; } }nml-0.2.4/regression/026_asl.nml0000644000061700006170000000100312036626442017064 0ustar abuildabuild00000000000000spritelayout layout1 { building { sprite: 0xA68 + animation_frame; } } item(FEAT_AIRPORTTILES, tile1) { graphics { layout1; } } spriteset(set2) { [] b: [] [] } spritelayout layout2 { building { sprite: set2(animation_frame); } building { sprite: set2(b); hide_sprite: nearby_tile_is_water(0, 0); } } item(FEAT_AIRPORTTILES, tile2) { graphics { layout2; } } item(FEAT_OBJECTS, tile2) { graphics { layout2; } } nml-0.2.4/regression/002_sounds.nml0000644000061700006170000000011212036626442017612 0ustar abuildabuild00000000000000param[0] = sound("beef.wav"); param[1] = import_sound("\12\34\56\78", 3); nml-0.2.4/regression/temperate_railwagons.png0000644000061700006170000001713412036626442022144 0ustar abuildabuild00000000000000PNG  IHDRLsRGBPLTE43 \"qOBe-ٹʈWʈ߼?aF|Y/dP##Ņ]²p9o1@Y|d_`ײ͞չLZf̟EMF`-??3{2?0gȓψop26-׳zfe H9iQesnF ^0̔=A s\T=񗂿}>;֏o/=B<"rYfă= 3 |fģ*n  +~P\Ie#qM+k_ʈ7Gջv)8z1x.Ũ>Y$7#h_q3:Ax_F|< \G;8k!^g(5-Vg^2KD. Oe㇭wgw߀00-`T)1osoG)[_E9G:,7>`x)9=Ԙ=67l<P )zIcbp7-K.T|)^gDLf`ip1pWr`]ڒK۽KSj:7ޥ`,@"wюn^zvgZKZ7M4O.I.5;n8Nٛ1ZKcEp7RsU4UmB׍=ԈR0Y |[AոBF!Ō߬՝óޥsjI$Y p4 h2^L sOՈ~gBmT79pn+-7@z'-| 'I_ƨi,ImhMtnwxՍz5@W9lm nOfKD՞nil^Ȝ!-mxyK\}nc]|5>Óxt%=-WH>w 3C^7pGH}!]ZJbpCE=3#.L!^1,m1pY0qT#v ;˥)Ti7ǙE\oxΐX}d_©d7ǻd#rLp` t-cucdsZ+[Acxpui8tӉa1A"YNۃ}}acWx< Ɩ;}YDn|[&%vIVjxdӊoC|#swi/1@7pCvE!p̈r _)r|>x,`24l"z1ow-.PXBn=hpu09]ښyKve!ޓXֻvsX5nGNx,K;2m?D9:ќp`Ùƅ~\!>^s:c k`a-l4 Px!>TϵKQk 3#~x,\j @Sc $3<-4*V-ŋc%V fuH/0S ,$dEdqE.RNqqWԋ625o{KqL.*ZEoOQ|.&=rH`m#rq{kSxA L׭/n\jy(~  2䗢qM`BQZ 󥯅;de0.mny{[kd mMpuX}`N^qzP#ғ# y!ܠEXP &uqPq>VFq崀eO(c )(?-_Nȥ⿆]`]#^׈oZ+YD J _#޵xw^X8gK$.ֈW]Zv|›`̴Y{b6#O|ZP>Zɾ߬ 5k}Pfh~Y+O6rH`0GOVi |=̠YJ5kSХq|Y+լ=ƪ7kSاZ|j !u"rh]{\:,Y;fdOY;feOY3ffOY3fgOYv5kOfUN[l*N'{?-$;G1f[ VN۝@wx[yBsfG@~CBluSl"Ha O?xHV~a'ftbM;NғY+WAd#rsv`8YZOɻwع1)w ;Oփ8én*'GY~.>=ⓧY끟n,7k6]HBf-mvF<;GҥpplC83#5kØt:O.-|=0 Ukspl _=ㄢxn `!bt61T^#aiYfk5k=7kK5k8pyf jz3aAZdYlfs + h=!a`O]`l盵Bȓh;f8Z~r}YӓF߬<Y+"y ߬-scUn*ƾ 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d20 1 * 231 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 03 "C" "PARA" "C" \d0 "T" "NAME" 7F "Name of the int setting" 00 "T" "DESC" 7F "Description of a setting" 00 "B" "MASK" \w1 00 "B" "LIMI" \w8 \d0 \d2 "B" "DFLT" \w4 \dx00000000 00 "C" \d1 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b1 \b0 \b1 "B" "DFLT" \w4 \dx00000000 00 "C" \d2 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b1 \b2 \b1 "B" "DFLT" \w4 \dx00000001 00 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "NML\14" "NML regression test" 00 "A test newgrf testing NML" 00 // param[16] = param[0] 3 * 5 0D 10 \D= 00 00 // param[17] = 0 4 * 9 0D 11 \D= FF 00 \dx00000000 5 * 6 09 01 01 \70 00 01 // param[17] = 1 6 * 9 0D 11 \D= FF 00 \dx00000001 // param[18] = 0 7 * 9 0D 12 \D= FF 00 \dx00000000 8 * 6 09 01 01 \70 02 01 // param[18] = 1 9 * 9 0D 12 \D= FF 00 \dx00000001 // param[19] = param[0] 10 * 9 0D 13 \D= 00 FE \dx014C4D4E // param[158] = (param[158] | 2) 11 * 9 0D 9E \D| 9E FF \dx00000002 // param[20] = 0 12 * 9 0D 14 \D= FF 00 \dx00000000 13 * 6 09 9E 01 \70 03 01 // param[20] = 1 14 * 9 0D 14 \D= FF 00 \dx00000001 // param[142] = -2 15 * 9 0D 8E \D= FF 00 \dxFFFFFFFE // param[21] = param[164] 16 * 5 0D 15 \D= A4 00 // param[66] = param[19] 17 * 9 0D 42 \D= 13 FE \dx0000FFFF // param[65] = (param[66] & 255) 18 * 9 0D 41 \D& 42 FF \dx000000FF // param[64] = (param[65] + 12) 19 * 9 0D 40 \D+ 41 FF \dx0000000C // param[22] = (1 << param[64]) 20 * 9 0D 16 \D<< FF 40 \dx00000001 nml-0.2.4/regression/expected/016_basic_airporttiles.nfo0000644000061700006170000000134212036626442023771 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 6 01 11 \b1 FF \wx0001 1 opengfx_trains_start.pcx 142 112 01 22 8 -3 -10 // Name: small_airport_tiles_graphics - feature 11 2 * 15 02 11 FE \b1 \dx00000F8D \dxC0000000 \b0 \b0 80 3 * 16 00 11 \b5 01 00 08 00 0F \wx0103 10 01 11 01 0E 01 4 * 7 03 11 01 00 \b0 \wx00FE // small_airport_tiles_graphics; nml-0.2.4/regression/expected/003_assignment.grf0000644000061700006170000000025112036626442022245 0ustar abuildabuild00000000000000       @ @ @@   @ @ nml-0.2.4/regression/expected/025_loop.nfo0000644000061700006170000000136112036626442021061 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel // param[64] = 0 0 * 9 0D 40 \D= FF 00 \dx00000000 1 * 2 10 10 // param[65] = (param[64] - 5) 2 * 9 0D 41 \D- 40 FF \dx00000005 // param[65] = (param[65] << -31) 3 * 9 0D 41 \Du<< 41 FF \dxFFFFFFE1 4 * 9 09 41 04 \7= \dx00000000 02 // param[64] = (param[64] + 1) 5 * 9 0D 40 \D+ 40 FF \dx00000001 6 * 6 09 9A 01 \71 00 10 nml-0.2.4/regression/expected/001_action8.grf0000644000061700006170000000017512036626442021445 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84testNML regression testA test newgrf testing NMLnml-0.2.4/regression/expected/004_deactivate.grf0000644000061700006170000000003412036626442022206 0ustar abuildabuild00000000000000ABCD EFGHnml-0.2.4/regression/expected/007_townnames.nfo0000644000061700006170000000136212036626442022124 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 52 0F 00 // A 02 04 00 05 0A "small" 00 0A "medium" 00 02 "big" 00 01 "" 00 03 05 05 0D "village" 00 0A "town" 00 01 "city" 00 1 * 20 0F 01 // 1 01 01 00 01 01 "tiny village" 00 2 * 73 0F 82 00 "US Stations" 00 1F "Test stationnnetjes" 00 7F "Test Stations" 00 00 01 03 0A 04 0A "MainCapital" 00 85 00 81 01 nml-0.2.4/regression/expected/009_replaceTTDsprite.nfo0000644000061700006170000000141212036626442023325 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d5 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 5 0A \b1 \b2 \w3092 4 opengfx_generic_trams1.pcx 48 56 01 18 8 -3 -10 5 opengfx_generic_trams1.pcx 48 56 01 18 8 -3 4 nml-0.2.4/regression/expected/013_train_callback.grf0000644000061700006170000001551312036626442023036 0ustar abuildabuild0000000000000066CINFOBVRSNBMINVBNPARBPALSWBBLTR84NMLNML regression testA test newgrf testing NML2 WDPRSCRPCMNTWOODLVSTSTELVEHIBRCKWOOLBUBLTOYSFZDRFRUTFRVGFOODOIL_GOODWATRMILKCOALIOREAORECLAYGRVLSANDGRAIRSGRMAIZCOREFERTCTCDSULPWHEARFPRCOLAPETRPAPRTOFFSUGRPASSMAILBATTSWETRUBRFMSPENSPMNSPFICRPLASPLSTRAILELRLMONOMGLVTRPD     0vzjj~ '6>װ0P(\)> j!hj!2PgPjiP-PjdV       `h~Ѐ  t!I@ ȟj XXXXX_j!j!h~ '4>0s jX````j4`-JjX,j( ׈X׈XXH( v jj}~ '6>׈Xװ0X(\,<Pe j!j!2PPjP-PjdV    ׈X XXXX ׈$(,X" "%&X`h  KI@  jX _j!j!h~ '4׈>XXX jX````j4`-JjX,j( nnnnmnoonlllllkklknkmmkkkmm5555ii5k n  5on5oofg oi5kkllmnii5 mi ~ mnno(llk5mmk55ii0lno5n<l@5m5)mn5l(+kkli6,(lj!i5kmmii55@lX.d  nnmmm nnnoo llmlllkkkkk55 k5555i@68ii h~Ѐi5kkiWill5imihlilk m5iii$$׈ˈXXXXאX_j!j!XmX׸~ mmmnmnlkkkli5mn055imm-ik5Hno505k5kikHRnojXHmm0ilimnlll0l׈kX4סJjXɈ( nn׈Xoon׈Xmmn@@o0X nmmnoooi5kkllmnii5 mi ~ mnno(l׈ mml׈Xװ0Xno5n<(5m5mn5l(B"*IHJIH      Ɉ#  @#)  j!j!  ~ ˈ˘FGIIGHHJ̸II00JI0IjX辐ȼǰHʡ0j4jH-Jj(  ( (wzX \ jj~ ( 0P>3˸S?FN6j!2WB"JN*DCB`h~Ѐ  Ȣ$%" !^[_bЉ XXXX Xj!j!  ~ @ACCABBDCV0CBDDɸC jX0)j4JjX0j(    GSG//   5G  Ht()0*@z &   ( 0L$%tNML Test bulk wagon t!!! 66 66 tnml-0.2.4/regression/expected/004_deactivate.nfo0000644000061700006170000000074712036626442022225 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 6 0E \b1 \dx44434241 1 * 10 0E \b2 \dx04030201 \dx48474645 nml-0.2.4/regression/expected/021_grf_parameter.nfo0000644000061700006170000000336712036626442022732 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d3 1 * 817 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 03 "C" "PARA" "C" \d0 "T" "NAME" 7F "Disable gridlines" 00 "T" "DESC" 7F "This setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this rule" 00 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b0 \b0 \b1 "B" "DFLT" \w4 \dx00000001 00 "C" \d1 "T" "NAME" 7F "Replace the transmitter tower by a rock" 00 "T" "DESC" 7F "Enable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers)" 00 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b0 \b1 \b1 "B" "DFLT" \w4 \dx00000000 00 "C" \d2 "T" "NAME" 7F "Landscape type" 00 "T" "DESC" 7F "Select the landscape (ground tile) type" 00 "B" "MASK" \w1 01 "B" "LIMI" \w8 \d0 \d1 "C" "VALU" "T" \d0 7F "normal (according to climate)" 00 "T" \d1 7F "alpine (temperate grass in arctic)" 00 "T" \d2 7F "temperate (not implemented)" 00 "T" \d3 7F "arctic (not implemented)" 00 "T" \d4 7F "tropical (not implemented)" 00 "T" \d5 7F "toyland (not implemented)" 00 00 "B" "DFLT" \w4 \dx00000000 00 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 // param[10] = param[1] 3 * 5 0D 0A \D= 01 00 nml-0.2.4/regression/expected/026_asl.grf0000644000061700006170000000055312036626442020666 0ustar abuildabuild00000000000000A&D`h %BBC  b)    %BBD  `)    nml-0.2.4/regression/expected/021_grf_parameter.grf0000644000061700006170000000160012036626442022712 0ustar abuildabuild000000000000001CINFOBVRSNBMINVBNPARCPARACTNAMEDisable gridlinesTDESCThis setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this ruleBTYPEBMASKBDFLTCTNAMEReplace the transmitter tower by a rockTDESCEnable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers)BTYPEBMASKBDFLTCTNAMELandscape typeTDESCSelect the landscape (ground tile) typeBMASKBLIMICVALUTnormal (according to climate)Talpine (temperate grass in arctic)Ttemperate (not implemented)Tarctic (not implemented)Ttropical (not implemented)Ttoyland (not implemented)BDFLTBPALSABBLTR84testNML regression testA test newgrf testing NML nml-0.2.4/regression/expected/002_sounds.grf0000644000061700006170000000010112036626442021401 0ustar abuildabuild00000000000000 I Jbeef.wav4Vxnml-0.2.4/regression/expected/005_error.nfo0000644000061700006170000000147612036626442021246 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 9 0B 00 1F 02 "zorg" 00 1 * 9 0B 00 7F 02 "care" 00 // param[64] = 14 2 * 9 0D 40 \D= FF 00 \dx0000000E // param[66] = (param[2] * 12) 3 * 9 0D 42 \D* 02 FF \dx0000000C // param[65] = (param[1] + param[66]) 4 * 5 0D 41 \D+ 01 42 5 * 66 0B 03 1F FF "De wissels zijn bevroren, onze excuses voor het ongemak." 00 "42" 00 40 41 6 * 42 0B 03 7F FF "Something bad (tm) has happened." 00 "42" 00 40 41 nml-0.2.4/regression/expected/014_read_special_param.grf0000644000061700006170000000076512036626442023704 0ustar abuildabuild00000000000000CINFOBVRSNBMINVBNPARCPARACTNAMEName of the int settingTDESCDescription of a settingBMASKBLIMIBDFLTCBTYPEBMASKBDFLTCBTYPEBMASKBDFLTBPALSABBLTR84NMLNML regression testA test newgrf testing NML        NML       B AB @A @nml-0.2.4/regression/expected/018_airport_tile.grf0000644000061700006170000000023412036626442022601 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84testNML regression testA test newgrf testing NMLhJ"nml-0.2.4/regression/expected/002_sounds.nfo0000644000061700006170000000111012036626442021406 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel // param[0] = 73 0 * 9 0D 00 \D= FF 00 \dx00000049 // param[1] = 74 1 * 9 0D 01 \D= FF 00 \dx0000004A 2 * 3 11 \w2 3 ** beef.wav 4 * 8 FE 00 \dx78563412 \wx0003 nml-0.2.4/regression/expected/023_engine_override.grf0000644000061700006170000000023712036626442023247 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84testNML regression testA test newgrf testing NMLtestABCDtest4Vxnml-0.2.4/regression/expected/012_basecost.nfo0000644000061700006170000000275412036626442021716 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d19 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "NML\12" "NML regression test" 00 "A test newgrf testing NML" 00 // param[64] = (param[2] + 8) 3 * 9 0D 40 \D+ 02 FF \dx00000008 4 * 7 06 40 01 FF \wx0006 FF 5 * 7 00 08 \b1 01 01 08 00 6 * 12 00 08 \b1 06 2A 08 06 06 06 06 06 06 // param[65] = (param[1] + 9) 7 * 9 0D 41 \D+ 01 FF \dx00000009 8 * 27 06 41 01 FF \wx0006 41 01 FF \wx0007 41 01 FF \wx0008 41 01 FF \wx0009 41 01 FF \wx000A FF 9 * 11 00 08 \b1 05 0F 08 00 00 00 00 00 10 * 7 00 08 \b1 01 34 08 15 // param[67] = param[11] 11 * 5 0D 43 \D= 0B 00 12 * 7 06 43 01 FF \wx0003 FF // param[66] = param[0] 13 * 5 0D 42 \D= 00 00 // param[69] = (param[11] + 1) 14 * 9 0D 45 \D+ 0B FF \dx00000001 15 * 7 06 45 01 FF \wx0003 FF // param[68] = param[0] 16 * 5 0D 44 \D= 00 00 // param[67] = (param[68] + 8) 17 * 9 0D 43 \D+ 44 FF \dx00000008 18 * 12 06 42 01 FF \wx0004 43 01 FF \wx0006 FF 19 * 7 00 08 \b1 01 00 08 00 nml-0.2.4/regression/expected/013_train_callback.nfo0000644000061700006170000001332712036626442023043 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d54 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "NML\13" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 206 00 08 \b1 32 00 09 "WDPR" "SCRP" "CMNT" "WOOD" "LVST" "STEL" "VEHI" "BRCK" "WOOL" "BUBL" "TOYS" "FZDR" "FRUT" "FRVG" "FOOD" "OIL_" "GOOD" "WATR" "MILK" "COAL" "IORE" "AORE" "CLAY" "GRVL" "SAND" "GRAI" "RSGR" "MAIZ" "CORE" "FERT" "CTCD" "SULP" "WHEA" "RFPR" "COLA" "PETR" "PAPR" "TOFF" "SUGR" "PASS" "MAIL" "BATT" "SWET" "RUBR" "FMSP" "ENSP" "MNSP" "FICR" "PLAS" "PLST" 4 * 26 00 08 \b1 05 00 12 "RAIL" "ELRL" "MONO" "MGLV" "TRPD" 5 * 6 01 00 \b8 FF \wx0004 6 temperate_railwagons.png 0 25 01 24 8 -3 -12 7 temperate_railwagons.png 16 25 01 17 22 -14 -9 8 temperate_railwagons.png 48 25 01 12 32 -16 -8 9 temperate_railwagons.png 96 25 01 17 22 -6 -9 10 temperate_railwagons.png 0 250 01 24 8 -3 -12 11 temperate_railwagons.png 16 250 01 17 22 -14 -9 12 temperate_railwagons.png 48 250 01 12 32 -16 -8 13 temperate_railwagons.png 96 250 01 17 22 -6 -9 14 arctic_railwagons.pcx 0 25 01 24 8 -3 -12 15 arctic_railwagons.pcx 16 25 01 17 22 -14 -9 16 arctic_railwagons.pcx 48 25 01 12 32 -16 -8 17 arctic_railwagons.pcx 96 25 01 17 22 -6 -9 18 arctic_railwagons.pcx 0 250 01 24 8 -3 -12 19 arctic_railwagons.pcx 16 250 01 17 22 -14 -9 20 arctic_railwagons.pcx 48 250 01 12 32 -16 -8 21 arctic_railwagons.pcx 96 250 01 17 22 -6 -9 22 temperate_railwagons.png 0 225 01 24 8 -3 -12 23 temperate_railwagons.png 16 225 01 17 22 -14 -9 24 temperate_railwagons.png 48 225 01 12 32 -16 -8 25 temperate_railwagons.png 96 225 01 17 22 -6 -9 26 temperate_railwagons.png 0 350 01 24 8 -3 -12 27 temperate_railwagons.png 16 350 01 17 22 -14 -9 28 temperate_railwagons.png 48 350 01 12 32 -16 -8 29 temperate_railwagons.png 96 350 01 17 22 -6 -9 30 temperate_railwagons.png 0 150 01 24 8 -3 -12 31 temperate_railwagons.png 16 150 01 17 22 -14 -9 32 temperate_railwagons.png 48 150 01 12 32 -16 -8 33 temperate_railwagons.png 96 150 01 17 22 -6 -9 34 temperate_railwagons.png 0 275 01 24 8 -3 -12 35 temperate_railwagons.png 16 275 01 17 22 -14 -9 36 temperate_railwagons.png 48 275 01 12 32 -16 -8 37 temperate_railwagons.png 96 275 01 17 22 -6 -9 // Name: bulk_wagon_coal_default_group - feature 00 38 * 13 02 00 FE \b2 \b2 \w0 \w1 \w0 \w1 // Name: bulk_wagon_coal_arctic_group - feature 00 39 * 13 02 00 FD \b2 \b2 \w2 \w3 \w2 \w3 // Name: bulk_wagon_coal2_group - feature 00 40 * 13 02 00 FC \b2 \b2 \w4 \w5 \w4 \w5 // Name: bulk_wagon_grain_group - feature 00 41 * 13 02 00 FB \b2 \b2 \w6 \w7 \w6 \w7 // Name: bulk_wagon_coal_default_switch 42 * 15 02 00 FC 80 02 \b0 04 \wx00FE \wx00FE \wx00FE // (2/3) -> (3/4): bulk_wagon_coal_default_group; \wx00FC // (1/3) -> (1/4): bulk_wagon_coal2_group; // Name: bulk_wagon_coal_climate_switch 43 * 23 02 00 FC 89 03 00 \dx00000003 \b1 \wx00FD \dx00000001 \dx00000001 // 1 .. 1: bulk_wagon_coal_arctic_group; \wx00FC // default: bulk_wagon_coal_default_switch; // Name: bulk_wagon_graphics_switch 44 * 23 02 00 FB 89 47 00 \dx000000FF \b1 \wx00FC \dx00000013 \dx00000013 // 19 .. 19: bulk_wagon_coal_climate_switch; \wx00FB // default: bulk_wagon_grain_group; // Name: bulk_wagon_cb_capacity_switch 45 * 83 02 00 FC 89 47 00 \dx000000FF \b7 \wx8019 \dx0000002F \dx0000002F // 47 .. 47: return 25; \wx8014 \dx0000000C \dx0000000C // 12 .. 12: return 20; \wx8014 \dx0000000D \dx0000000D // 13 .. 13: return 20; \wx8019 \dx00000019 \dx00000019 // 25 .. 25: return 25; \wx8019 \dx0000001B \dx0000001B // 27 .. 27: return 25; \wx8014 \dx0000001A \dx0000001A // 26 .. 26: return 20; \wx8019 \dx00000020 \dx00000020 // 32 .. 32: return 25; \wx0000 // default: CB_FAILED; // Name: bulk_wagon_cb_weight_switch 46 * 53 02 00 FD 89 47 00 \dx000000FF \b4 \wx8012 \dx00000013 \dx00000013 // 19 .. 19: return 18; \wx8012 \dx0000000C \dx0000000C // 12 .. 12: return 18; \wx8012 \dx0000000D \dx0000000D // 13 .. 13: return 18; \wx8012 \dx0000001A \dx0000001A // 26 .. 26: return 18; \wx0000 // default: CB_FAILED; 47 * 72 00 00 \b26 01 FF \wx0074 06 0F 28 \wx0010 15 FF 29 \wx01CB 15 FF 1D \dx00003003 15 FF 12 FD 2A \dx000A7A40 04 FF 26 00 03 1E 02 00 07 0A 17 B6 0D 05 09 \wx0000 1C 28 1E 08 05 00 0B \wx0000 0E \dx00004C30 14 1E 16 19 24 00 25 00 48 * 27 04 00 7F 01 FF \wx0074 "NML Test bulk wagon" 00 49 * 9 00 00 \b1 01 FF \wx0074 1E 08 // Name: @action3_0 50 * 33 02 00 FD 89 10 00 \dx000000FF \b2 \wx00FC \dx00000014 \dx00000014 // bulk_wagon_cb_capacity_switch; \wx00FD \dx00000016 \dx00000016 // bulk_wagon_cb_weight_switch; \wx00FB // bulk_wagon_graphics_switch; // Name: @action3_1 51 * 33 02 00 FE 89 10 00 \dx000000FF \b2 \wx801E \dx00000014 \dx00000014 // return 30; \wx8019 \dx00000016 \dx00000016 // return 25; \wx00FB // bulk_wagon_graphics_switch; // Name: @action3_2 52 * 33 02 00 FD 89 0C 00 \dx0000FFFF \b2 \wx00FC \dx00000015 \dx00000015 // bulk_wagon_cb_capacity_switch; \wx00FD \dx00000036 \dx00000036 // @action3_0; \wx00FB // bulk_wagon_graphics_switch; // Name: @action3_3 53 * 23 02 00 FB 89 0C 00 \dx0000FFFF \b1 \wx00FE \dx00000036 \dx00000036 // @action3_1; \wx00FB // bulk_wagon_graphics_switch; 54 * 12 03 00 01 FF \wx0074 \b1 FF \wx00FB // @action3_3; \wx00FD // @action3_2; nml-0.2.4/regression/expected/003_assignment.nfo0000644000061700006170000000234412036626442022256 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel // param[0] = 3 0 * 9 0D 00 \D= FF 00 \dx00000003 // param[1] = 4 1 * 9 0D 01 \D= FF 00 \dx00000004 // param[2] = (param[0] + param[1]) 2 * 5 0D 02 \D+ 00 01 // param[3] = (param[0] * 3) 3 * 9 0D 03 \D* 00 FF \dx00000003 // param[4] = 11 4 * 9 0D 04 \D= FF 00 \dx0000000B // param[5] = (param[4] & 3) 5 * 9 0D 05 \D& 04 FF \dx00000003 // param[64] = (param[1] + param[2]) 6 * 5 0D 40 \D+ 01 02 // param[6] = (param[64] + param[3]) 7 * 5 0D 06 \D+ 40 03 // param[64] = param[3] 8 * 5 0D 40 \D= 03 00 9 * 7 06 40 01 FF \wx0003 FF // param[7] = param[0] 10 * 5 0D 07 \D= 00 00 11 * 7 06 00 01 FF \wx0001 FF // param[0] = 5 12 * 9 0D 00 \D= FF 00 \dx00000005 // param[64] = param[3] 13 * 5 0D 40 \D= 03 00 14 * 12 06 00 01 FF \wx0001 40 01 FF \wx0003 FF // param[0] = param[0] 15 * 5 0D 00 \D= 00 00 nml-0.2.4/regression/expected/009_replaceTTDsprite.grf0000644000061700006170000000056312036626442023327 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSWBBLTR84testNML regression testA test newgrf testing NML    0YjbȃʃȄʄH{ǵ )  0YjbȃʃȄʄH{ǵ )nml-0.2.4/regression/expected/027_airport_layout.nfo0000644000061700006170000000152512036626442023171 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 6 01 11 \b1 FF \wx0001 1 * 1 00 // Name: small_airport_tile_layout - feature 11 2 * 25 02 11 FE \b2 \dx80000000 \dxC0008000 \b32 \b16 80 \dx80000000 \b0 \b0 \b0 \b16 \b16 \b16 3 * 16 00 11 \b5 01 00 08 00 0F \wx0103 10 01 11 01 0E 01 4 * 7 03 11 01 00 \b0 \wx00FE // small_airport_tile_layout; 5 * 34 00 0D \b2 01 00 08 00 0A \b1 \d21 00 00 00 FE \wx0000 01 00 FE \wx0000 02 00 FE \wx0000 03 00 46 00 80 nml-0.2.4/regression/expected/011_snowline.nfo0000644000061700006170000000353512036626442021746 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d3 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "sn\00\00" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 390 00 08 \b1 01 00 10 18 18 18 18 18 18 18 18 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 18 18 18 18 18 18 20 20 20 20 20 28 28 28 28 28 30 30 30 30 30 30 38 38 38 38 38 38 38 38 38 40 40 40 40 40 48 48 48 48 48 48 50 50 50 50 50 58 58 58 58 58 58 60 60 60 60 60 68 68 68 68 68 68 70 70 70 70 70 70 78 78 78 78 78 80 80 80 80 80 88 88 88 88 88 88 90 90 90 90 90 90 90 98 98 98 98 98 A0 A0 A0 A0 A0 A0 A8 A8 A8 A8 A8 B0 B0 B0 B0 B0 B0 B8 B8 B8 B8 B8 C0 C0 C0 C0 C0 C0 C8 C8 C8 C8 C8 C8 D0 D0 D0 D0 D0 D8 D8 D8 D8 D8 E0 E0 E0 E0 E0 E0 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E8 E0 D8 D8 D0 C8 C0 B8 B8 B0 A8 A0 98 98 90 88 80 78 70 70 68 60 58 50 50 48 40 38 30 30 28 28 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 nml-0.2.4/regression/expected/011_snowline.grf0000644000061700006170000000100612036626442021731 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84snNML regression testA test newgrf testing NML (((((000000888888888@@@@@HHHHHHPPPPPXXXXXX`````hhhhhhppppppxxxxxxpph`XPPH@800(( nml-0.2.4/regression/expected/015_basic_object.grf0000644000061700006170000000035212036626442022511 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML!MiscellaneousBasic objectH' MISC   ɖ  nml-0.2.4/regression/expected/006_vehicle.nfo0000644000061700006170000000340012036626442021522 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d20 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 30 00 08 \b1 06 00 09 "PASS" "MAIL" "GOOD" "IORE" "GOLD" "FOOD" // param[0] = 9 4 * 9 0D 00 \D= FF 00 \dx00000009 // param[64] = (param[0] * 5) 5 * 9 0D 40 \D* 00 FF \dx00000005 6 * 7 06 40 01 FF \wx002C FF 7 * 57 00 01 \b21 01 FF \wx0059 06 03 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 98 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 00 1D \wx0003 10 FF 1E \wx0000 10 FF 1C 01 8 * 27 04 01 7F 01 FF \wx0059 "Foster Express Tram" 00 9 * 23 04 01 1F 01 FF \wx0059 "Foster Sneltram" 00 10 * 6 01 01 \b1 FF \wx0008 11 opengfx_generic_trams1.pcx 48 56 01 18 8 -3 -10 12 opengfx_generic_trams1.pcx 64 56 01 19 20 -14 -5 13 opengfx_generic_trams1.pcx 96 56 01 15 28 -14 -8 14 opengfx_generic_trams1.pcx 144 56 01 19 20 -6 -7 15 opengfx_generic_trams1.pcx 176 56 01 18 8 -3 -10 16 opengfx_generic_trams1.pcx 192 56 01 19 20 -14 -9 17 opengfx_generic_trams1.pcx 224 56 01 15 28 -14 -8 18 opengfx_generic_trams1.pcx 272 56 01 19 20 -6 -7 // Name: foster_express_set - feature 01 19 * 9 02 01 FE \b1 \b1 \w0 \w0 20 * 9 03 01 01 FF \wx0059 \b0 \wx00FE // foster_express_set; nml-0.2.4/regression/expected/023_engine_override.nfo0000644000061700006170000000136512036626442023256 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 14 00 08 \b1 01 00 11 \dx74736574 \dx44434241 4 * 14 00 08 \b1 01 00 11 \dx74736574 \dx78563412 nml-0.2.4/regression/expected/016_basic_airporttiles.grf0000644000061700006170000000033312036626442023764 0ustar abuildabuild00000000000000 9/!@Y`P0JhPq nml-0.2.4/regression/expected/027_airport_layout.grf0000644000061700006170000000016112036626442023160 0ustar abuildabuild00000000000000 "  Fnml-0.2.4/regression/expected/012_basecost.grf0000644000061700006170000000051312036626442021701 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84NMLNML regression testA test newgrf testing NML @@ * A AAAA A 4 C C B E E D CD BCnml-0.2.4/regression/expected/022_disable_item.grf0000644000061700006170000000043712036626442022525 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSABBLTR84testNML regression testA test newgrf testing NML|t    nml-0.2.4/regression/expected/020_recolour.grf0000644000061700006170000000473412036626442021740 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSWBBLTR84testNML regression testA test newgrf testing NML    !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~Ų   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~>?@ABCDE  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~Ś  0YjbȃʃȄʄH{ǵ )ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H( /sj ؈ 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d19 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: foster_express_articulated_parts 3 * 23 02 01 FE 89 10 00 \dxFFFFFFFF \b1 \wx8058 \dx00000001 \dx00000003 // 1 .. 3: return 88; \wx80FF // default: return 255; 4 * 66 00 01 \b24 01 FF \wx0058 06 0F 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 98 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 2D 1D \wx0001 10 FF 1E \wx0000 10 FF 16 \dx00000000 10 FF 1C 01 17 10 5 * 27 04 01 7F 01 FF \wx0058 "Foster Express Tram" 00 6 * 23 04 01 1F 01 FF \wx0058 "Foster Sneltram" 00 7 * 6 01 01 \b1 FF \wx0008 8 tram_foster_express.png 48 1 01 18 8 -3 -10 9 tram_foster_express.png 64 1 01 18 20 -14 -5 10 tram_foster_express.png 96 1 01 15 28 -14 -8 11 tram_foster_express.png 144 1 01 18 20 -6 -7 12 tram_foster_express.png 176 1 01 18 8 -3 -10 13 tram_foster_express.png 192 1 01 18 20 -14 -9 14 tram_foster_express.png 224 1 01 15 28 -14 -8 15 tram_foster_express.png 272 1 01 18 20 -6 -7 // Name: foster_express_set - feature 01 16 * 9 02 01 FD \b1 \b1 \w0 \w0 17 * 9 00 01 \b1 01 FF \wx0058 17 10 // Name: @action3_0 18 * 23 02 01 FD 89 0C 00 \dx0000FFFF \b1 \wx00FE \dx00000016 \dx00000016 // foster_express_articulated_parts; \wx00FD // foster_express_set; 19 * 9 03 01 01 FF \wx0058 \b0 \wx00FD // @action3_0; nml-0.2.4/regression/expected/010_liveryoverride.grf0000644000061700006170000001163412036626442023154 0ustar abuildabuild00000000000000#6CINFOBVRSNBMINVBNPARBPALSWBBLTR84testNML regression testA test newgrf testing NML 9/!@Y`P0JhPq CȘɠ&̸9 ʈ N)b? Xa   <  d  (a  (& !Ў( (  9_1|  ( ǘ': X ,.;<A̾) j<8D j:?BK (X        >  (! X( (! ((((X !!!!!jj X׈X ʈˈ-H1{ (((]^D X!!(X !(( (Xj(WjXj!!  j(!Xjj- j X *.Z XXjXر ؈XZ؈ס!ȈטFF  &7 (8OQ/Z   `Y#(  jX! j j j؈Xjj j؈X ƈɈ&>',I7)(K6I  (s  !׈( jX (؈jjj  ( ؈ & Xة׉/A  ( ! (T@#CF ʈX!-K̈j ˸L!|(  j    Xa  ! ! (!!!(( ( ׈ jj XjC ɘ . E,!!* !!\rY). X j[ Xjj !.XXjD(k !((ظ)ש DX&׈׈XjXj& j E8ju ׃Xˈ~ ׈XXX(XjXj (((D jXxR(נ XXjj!XXx( j!׃dׄxX9ɰXX-xTU  ׈ XXXXjjjj:jj \&)   omG b     GM! !j ~Xjj ׈X 0׈j0( _HXjX `ɠXɰHjjjXȸ`jXFxׁ׃0XCnr׈׈XjXj& j E8ju ׃Xˈ~ ׈XXX(XjXj (((D jXxR(נ XXjj!XXx( j!׃dׄxX9ɰXX-xTU  ׈ XXXXjjjj:jj \&)   omG b     GM! !j ~Xjj ׈X 0׈j0( _HXjX `ɠXɰHjjjXȸ`jXFxׁ׃0XCnr  0@P^ni r 4 ƈɈʰ;*6&  (n6  !p   jH  6 H ! ׈ס     $<:4@G      !     (! (!! (XX  XjH ʈːʈɈa(((X!!Xo X !(!Bo*j jB-jjBXB((B('װX!Ƙ!96  0@P^ni r 4 ƈɈʰ;*6&  (n6  !p   jH  6 H ! ׈ס     $<:4@G      !     (! (!! (XX  XjH ʈːʈɈa(((X!!Xo X !(!Bo*j jB-jjBXB((B('װX!Ƙ!96    '  '() nml-0.2.4/regression/expected/001_action8.nfo0000644000061700006170000000121312036626442021443 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d2 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 nml-0.2.4/regression/expected/007_townnames.grf0000644000061700006170000000024012036626442022112 0ustar abuildabuild000000000000004 small mediumbig village towncitytiny villageIUS StationsTest stationnnetjesTest Stations  MainCapitalnml-0.2.4/regression/expected/015_basic_object.nfo0000644000061700006170000000177612036626442022530 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d6 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "NML\15" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 33 04 0F FF 02 \wxD000 "Miscellaneous" 00 "Basic object" 00 // Name: obj_basic_tile - feature 0F 4 * 18 02 0F FE \b1 \dx0000058C \dx03078A48 \b0 \b0 \b0 \b16 \b16 \b30 5 * 39 00 0F \b11 01 00 08 "MISC" 09 \wxD000 0A \wxD001 0B 07 0C 11 0E \dx000A96C9 0F \dx000AC196 0D 01 14 01 10 \wx0800 16 04 6 * 7 03 0F 01 00 \b0 \wx00FE // obj_basic_tile; nml-0.2.4/regression/expected/019_switch.grf0000644000061700006170000000047312036626442021413 0ustar abuildabuild00000000000000 6CINFOBVRSNBMINVBNPARBPALSABBLTR84testNML regression testA test newgrf testing NML3ИcoaldiamondsExtra info for coal mine: {%      !@"  + 77::;; nml-0.2.4/regression/expected/018_airport_tile.nfo0000644000061700006170000000146712036626442022616 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: dirt_runway_sw_snow - feature 11 3 * 18 02 11 FE \b1 \dx00000000 \dx03224A68 \b0 \b15 \b0 \b16 \b1 \b6 4 * 7 03 11 01 00 \b0 \wx00FE // dirt_runway_sw_snow; nml-0.2.4/regression/expected/005_error.grf0000644000061700006170000000026012036626442021230 0ustar abuildabuild00000000000000 zorg care @ B  ABB De wissels zijn bevroren, onze excuses voor het ongemak.42@A* Something bad (tm) has happened.42@Anml-0.2.4/regression/expected/024_conditional.nfo0000644000061700006170000000113512036626442022411 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel // param[64] = param[0] 0 * 5 0D 40 \D= 00 00 1 * 9 09 40 04 \7= \dx00000000 01 // param[1] = 1 2 * 9 0D 01 \D= FF 00 \dx00000001 // param[3] = 1 3 * 9 0D 03 \D= FF 00 \dx00000001 nml-0.2.4/regression/expected/006_vehicle.grf0000644000061700006170000000355112036626442021525 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSWBBLTR84testNML regression testA test newgrf testing NML PASSMAILGOODIOREGOLDFOOD @@,9Y(  HL XMYFoster Express TramYFoster Sneltram  0YjbȃʃȄʄH{ǵ )ˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H( /sj ؈ 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d19 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 // param[3] = param[\DR] 3 * 9 0D 03 \D= \DR FE \dx000308FF 4 * 7 06 03 02 FF \wx0003 FF 5 * 5 0A \b1 \b3 \w0 6 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 B2 D9 0A 0B 0C 0D 0E 0F CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 7 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 3E 3F 40 41 42 43 44 45 CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 8 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 9A 9B 9C 9D 9E 9F A0 A1 CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 9 * 6 01 01 \b1 FF \wx0008 10 opengfx_generic_trams1.pcx 48 56 01 18 8 -3 -10 11 opengfx_generic_trams1.pcx 64 56 01 19 20 -14 -5 12 opengfx_generic_trams1.pcx 96 56 01 15 28 -14 -8 13 opengfx_generic_trams1.pcx 144 56 01 19 20 -6 -7 14 opengfx_generic_trams1.pcx 176 56 01 18 8 -3 -10 15 opengfx_generic_trams1.pcx 192 56 01 19 20 -14 -9 16 opengfx_generic_trams1.pcx 224 56 01 15 28 -14 -8 17 opengfx_generic_trams1.pcx 272 56 01 19 20 -6 -7 // Name: foster_express_group - feature 01 18 * 9 02 01 FE \b1 \b1 \w0 \w0 19 * 9 03 01 01 FF \wx0058 \b0 \wx00FE // foster_express_group; nml-0.2.4/regression/expected/022_disable_item.nfo0000644000061700006170000000217212036626442022527 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d5 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 124 00 00 \b1 74 FF \wx0000 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 * 7 00 0A \b1 01 0C 08 FF 5 * 22 00 0B \b2 03 0A 08 FF FF FF 17 \dx00000000 \dx00000000 \dx00000000 nml-0.2.4/regression/expected/010_liveryoverride.nfo0000644000061700006170000000466212036626442023163 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d35 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 6 01 00 \b3 FF \wx0008 4 opengfx_trains_start.pcx 142 112 01 22 8 -3 -10 5 opengfx_trains_start.pcx 158 112 01 15 21 -14 -7 6 opengfx_trains_start.pcx 190 112 01 12 31 -16 -8 7 opengfx_trains_start.pcx 238 112 01 16 21 -6 -7 8 opengfx_trains_start.pcx 270 112 01 24 8 -3 -10 9 opengfx_trains_start.pcx 286 112 01 16 21 -15 -6 10 opengfx_trains_start.pcx 318 112 01 12 32 -16 -8 11 opengfx_trains_start.pcx 366 112 01 15 21 -6 -7 12 arctic_railwagons.pcx 0 0 01 24 8 -3 -12 13 arctic_railwagons.pcx 16 0 01 17 22 -14 -9 14 arctic_railwagons.pcx 48 0 01 12 32 -16 -8 15 arctic_railwagons.pcx 96 0 01 17 22 -6 -9 16 arctic_railwagons.pcx 0 0 01 24 8 -3 -12 17 arctic_railwagons.pcx 16 0 01 17 22 -14 -9 18 arctic_railwagons.pcx 48 0 01 12 32 -16 -8 19 arctic_railwagons.pcx 96 0 01 17 22 -6 -9 20 opengfx_trains_start.pcx 142 139 01 21 8 -3 -10 21 opengfx_trains_start.pcx 158 139 01 15 20 -13 -7 22 opengfx_trains_start.pcx 190 139 01 10 28 -12 -6 23 opengfx_trains_start.pcx 238 139 01 16 20 -6 -7 24 opengfx_trains_start.pcx 270 139 01 21 8 -3 -10 25 opengfx_trains_start.pcx 286 139 01 15 20 -15 -6 26 opengfx_trains_start.pcx 318 139 01 10 28 -16 -6 27 opengfx_trains_start.pcx 366 139 01 16 20 -6 -7 // Name: turbotrain_engine_group - feature 00 28 * 9 02 00 FE \b1 \b1 \w0 \w0 // Name: normal_passenger_group - feature 00 29 * 9 02 00 FD \b1 \b1 \w1 \w1 // Name: turbotrain_passenger_group - feature 00 30 * 9 02 00 FC \b1 \b1 \w2 \w2 31 * 11 00 00 \b2 01 FF \wx0014 12 FD 27 04 32 * 9 03 00 01 FF \wx0014 \b0 \wx00FE // turbotrain_engine_group; 33 * 9 03 00 81 FF \wx001B \b0 \wx00FC // turbotrain_passenger_group; 34 * 21 00 00 \b6 01 FF \wx001B 12 FD 27 04 28 \wx0001 15 FF 29 \wx0000 15 FF 35 * 9 03 00 01 FF \wx001B \b0 \wx00FD // normal_passenger_group; nml-0.2.4/regression/expected/008_railtypes.nfo0000644000061700006170000000074012036626442022125 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 19 00 10 \b3 01 00 08 "RAIL" 14 \wx0028 0E \b1 "ELRL" nml-0.2.4/regression/expected/019_switch.nfo0000644000061700006170000000277312036626442021424 0ustar abuildabuild00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 7) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel 0 * 4 \d9 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 07 "test" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 51 04 00 FF 03 \wxD000 "\98coal" 00 "\98diamonds" 00 "\98Extra info for coal mine: \7B" 00 // Name: coal_mine_subtype_switch 4 * 37 02 0A FE 89 1A 20 \dx00000004 \2psto 1A 20 \dx00000000 \2r 02 00 \dx000000FF \b1 \wx8000 \dx00000000 \dx0000000A // 0 .. 10: return string(STR_COALMINE_MONTH_0_10); \wx8001 // default: return string(STR_COALMINE_MONTH_11); 5 * 9 00 0A \b2 01 00 08 00 09 00 6 * 9 00 0A \b2 01 00 21 40 22 03 // Name: @return_action_0 7 * 13 02 0A FD 89 10 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @action3_0 8 * 43 02 0A FD 89 0C 00 \dx0000FFFF \b3 \wx00FE \dx00000037 \dx00000037 // coal_mine_subtype_switch; \wx8002 \dx0000003A \dx0000003A // return string(STR_COALMINE_EXTRA_TEXT); \wx00FD \dx0000003B \dx0000003B // return var[0x10, 0, 1] \wx0000 // CB_FAILED; 9 * 7 03 0A 01 00 \b0 \wx00FD // @action3_0; nml-0.2.4/regression/expected/008_railtypes.grf0000644000061700006170000000003412036626442022115 0ustar abuildabuild00000000000000RAIL(ELRLnml-0.2.4/regression/expected/017_articulated_tram.grf0000644000061700006170000000354212036626442023434 0ustar abuildabuild000000000000006CINFOBVRSNBMINVBNPARBPALSWBBLTR84testNML regression testA test newgrf testing NMLXBX(  HL XM-XFoster Express TramXFoster Sneltram  0YjbȃʃȄʄH{ǵ )pˠˠ̨9j7ʄ68ʨ%$ǐ ʃʸ$ ˂XȄ6 ̂XjɃ+YHȵZ6H( dj ؈ 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel // Name: layout1 - feature 11 0 * 23 02 11 FE \b65 \dx00000000 \wx0000 \dx00000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 // Name: layout1@registers - feature 11 1 * 38 02 11 FE 89 44 60 \dx000000FF \dx00000A68 \dx00000001 \2sto 1A 00 \dx00000080 \b1 \wx00FE \dx00000000 \dx00000000 \wx00FE // 2 * 7 03 11 01 00 \b0 \wx00FE // layout1; 3 * 6 01 0F \b1 FF \wx0003 4 * 1 00 5 * 1 00 6 * 1 00 // Name: layout2 - feature 0F 7 * 37 02 0F FE \b66 \dx00000000 \wx0000 \dx80000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 \dx80000000 \wx0003 \b0 \b0 \b0 \b16 \b16 \b16 81 82 // Name: layout2@registers - feature 0F 8 * 66 02 0F FE 89 43 20 \dx000000FF \2sto 1A 20 \dx00000080 \2r 62 00 29 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000082 \b1 \wx00FE \dx00000000 \dx00000000 \wx00FE // 9 * 6 01 11 \b1 FF \wx0003 10 * 1 00 11 * 1 00 12 * 1 00 // Name: layout2 - feature 11 13 * 37 02 11 FD \b66 \dx00000000 \wx0000 \dx80000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 \dx80000000 \wx0003 \b0 \b0 \b0 \b16 \b16 \b16 81 82 // Name: layout2@registers - feature 11 14 * 66 02 11 FD 89 44 20 \dx000000FF \2sto 1A 20 \dx00000080 \2r 60 00 29 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000082 \b1 \wx00FD \dx00000000 \dx00000000 \wx00FD // 15 * 7 03 11 01 01 \b0 \wx00FD // layout2; 16 * 7 03 0F 01 01 \b0 \wx00FE // layout2; nml-0.2.4/regression/tram_foster_express.png0000644000061700006170000000461512036626442022026 0ustar abuildabuild00000000000000PNG  IHDRs*vK+sRGBPLTE4V;{Zs$6g:[?07Zf:Vcq}nPs}ϠQgs;k@d:j3WFk{!ŏ]"ωchS}1-vJ f,v`s, e5GKLoi}6VOχha kV{{Ʉh\ỉ&Vq0KSkO̅{myL_B8 H'4eږpmi_Fوo1'ԣG`nF2 Sb{\ӯo3`$f'ԋ~9s+H}6A!֚|3扶`h[Öt9ϝ+oE372GBj07Eq*}秵퓗NÜ+qΨmkߚc^Z]\W+}{,ϯ=ⶻt5mD=Wjmq9mgA,āycď/\GV%>07K߽=\[[5m9Ƽ:ā:Y4j_AWxBV pkPV7ejelTآ.έyۢZ[u4א:jciՈzNV ]ofڰȹu\1 67g~}U;j{h+$ VA9i[e_Հ->S~u=ۙjHݔ =ޯmk{h{~ļFWھ׋NWϺۮ{C @J]8Xۡ|w@Q+j?Sנqr^k۽gR'nI۾0w>O)桶U8TxoEmiYm+Ow W~bm;6Oghvu&#܎k] E`DPyv 2"%~+lq7"/1a>=|=1_]0sD(s yZGm*3kyV-?NI?ʜI}s]Ҕt2ib-s}R$櫗9"D,؆ Bzrw2 -Tbt[JeՓ{ul%i&$҃K<Ѥtyp%s݆:1t.1RMI<o%=_qtQ$& l++PӺ"ݓlP8RrTK$濒MIENDB`nml-0.2.4/regression/005_error.nml0000644000061700006170000000031312036626441017435 0ustar abuildabuild00000000000000// Regression test for error(..) statements (ActionB) error(NOTICE, USED_WITH, string(STR_REGRESSION_CARE)); error(FATAL, string(STR_REGRESSION_ERROR), string(STR_ANSWER), 14, param[1] + 12 * param[2]); nml-0.2.4/regression/020_recolour.nml0000644000061700006170000000232212036626441020135 0ustar abuildabuild00000000000000grf { grfid: "test"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template company_recolour(offset) { recolour_sprite { 0xC6..0xCD: offset..offset+7; } } param[3] = reserve_sprites(3); replace(param[3]) { recolour_sprite { 0xC6: 0x28; 0xC7: 0xF5; 0xC8..0xCD: 0x0A..0x0F; } company_recolour(0x3E) company_recolour(0x9A) } // To write any recolour sprite to the output nmlc needs to know which palette // to use. One option would be to specify the palette via the commandline, but // for now just include some graphics so nmlc can determine the palette from that. // Foster Express tram spriteset(foster_express_set, "opengfx_generic_trams1.pcx") { [ 48,56, 8,18, -3,-10] [ 64,56, 20,19, -14, -5] [ 96,56, 28,15, -14, -8] [144,56, 20,19, -6, -7] [176,56, 8,18, -3,-10] [192,56, 20,19, -14, -9] [224,56, 28,15, -14, -8] [272,56, 20,19, -6, -7] } spritegroup foster_express_group { loading: foster_express_set; loaded: foster_express_set; } item(FEAT_ROADVEHS, foster_express_tram) { graphics { foster_express_group; } }nml-0.2.4/regression/opengfx_generic_trams1.pcx0000644000061700006170000003375112036626442022372 0ustar abuildabuild00000000000000 r,,sjjjjjX؈XX؈XˆXXˆXjX؈XjjXˆXňXjXXˆˆXjÈ؈XjXXˆˆXˆX؈XÈjXXˆ؃X؈؈؈؈ˆĈXXˆXj؈XˆjXXˆ؃X؈؈؈؈ˆĈXˆjXˆXj؈XˆjXXˆ؃Xj؈ˆ؈XXˆXjX؈ˆXˆXj؈XˆjXXˆ؃Xj؈ˆ؈XXˆXjX؈ˆjXˆX؈XˆjXjXˆ؃Xjˆ؈ɄɄɄɄɄ˄˄ˈˆjXˆXj؈XˆjXXˆ؃Xjˆ؈ɄɄɄɄɄ˄˄ˈ ˆjXˆX؈XÈjXXˆ؃Xjˆ ؈XjXjXjXjXjXˆjXˆXj؈XÈjXXˆ؃Xjˆ ؈XjXjXjXjXjXȄ ˆjX؈X؈X؈jXXˆ؃Xjˆ j j ˆjXˆXj؈XˆjXXˆ؃Xjˆ j jɄ ˆjXň؈X؈jXX؃Xjˆ  ƒȄ ˆjX؈X؈XÈjXX؃Xjˆ  ƒXɄ ĈXˁ؈X؈jXňXjˆ ʅ      Ʉ ˆjXň؈XˆjXňXjˆ ʅ      jXɄ ˆˁƒ؈X؈jX؀ɈXjˆ ʂXXɄ ĈX˃؈XˆjX؀ɈXjˆ ʂXjɄ ƒ؁X؈jXÀXˆ ̂XjjXɄ ؈Xˀ؁XˆjXÀXˆ ̂XjɄ ؁ˆ؈X€ Ȃj˂X̂XjɄ ؁ĈX€ Ȃj˂X̂XXʵÁjɁƒ jˆjjˆjɄ ƒÁɁƒ jˆjjˆjjX؃XȵɄ˂XXɄ4XXCɄ˂Xj€„Xj˂XjjXjX€„Xj˂XjXjɂjjjX4XjɂjµXj (XjjjjjjʄjʄXXƒʄȃƒʄʄɄƒʄˀƒʄɄƒʄ˂XˁˁˁˁˁˁɄƒʄ˄˄˄˄˄ƒXȄƒʃDŽ̂Xj˃˃˃˃˃˃Ʉƒʄʀ˅˅˅˅˅˅jXɄƒʃʄɃDžɂjɄƒ́ɀ˄jɄʄjȄXȄƒǀÁÀʄ˂Xʵȃʃ˂X˂X̂XjXɄƒǀ€Ʉ̂XjXXXȄʄȵ˂XjjˆjjˆjjɄǁ€„ɁƒɅȂjjˆjjˆjjXʵɂjʀɄʃjǵʷX44C˂XjXʂXjjXɂj!j!!jj!!j!jjj!!!!j؈jjjj!jjɈjjɈjjjjjjjjjjj!jʀ؈ʄʈ€jDŽʈjʄʁ˃ʄÀɃjʄʃ„̄ƒʄÁɄƒʈʄʄƒɄƒʄƒ˄Ʉƒjʄ˂ˁ́ˁˁƒɄƒ̀ʄƒ˄˅̅˅ׂȄƒʃDŽʂjɃ̃̃̃̃Ʉƒǁ€˄̅ɄƒʃʄɃDžɂj€Ȅɀǀƒʅ€ɄʄȄ4ׂȁǁĄǀƒ„˂jCʵȃʃ˂ȃńǀăĄjׂȄʄȵɂjjˆjɂjˆjÄ4ǀĄCĄjˆjjˆjʵɂɃ jńXj„ǵʷ4 jƄXjC˂j jXjjC4Xj ( 4 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>>\n' '// Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C\n' '// Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D%\n' '// Format: spritenum pcxfile xpos ypos compression ysize xsize xrel yrel\n\n') return handle def print_byte(self, value): value = self.prepare_byte(value) self.file.write("\\b" + str(value) + " ") def print_bytex(self, value, pretty_print = None): value = self.prepare_byte(value) if pretty_print is not None: self.file.write(pretty_print + " ") return self.file.write("%02X " % value) def print_word(self, value): value = self.prepare_word(value) self.file.write("\\w%d " % value) def print_wordx(self, value): value = self.prepare_word(value) self.file.write("\\wx%04X " % value) def print_dword(self, value): value = self.prepare_dword(value) self.file.write("\\d%d " % value) def print_dwordx(self, value): value = self.prepare_dword(value) self.file.write("\\dx%08X " % value) def print_string(self, value, final_zero = True, force_ascii = False): assert self._in_sprite self.file.write('"') if not grfstrings.is_ascii_string(value): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") self.file.write('\xC3\x9E'.decode('utf-8')) self.file.write(value.replace('"', '\\"')) self._byte_count += grfstrings.get_string_size(value, final_zero, force_ascii) self.file.write('" ') if final_zero: self.print_bytex(0) # get_string_size already includes the final 0 byte # but print_bytex also increases _byte_count, so decrease # it here by one to correct it. self._byte_count -= 1 def print_decimal(self, value): assert self._in_sprite self.file.write(str(value) + " ") def newline(self, msg = "", prefix = "\t"): if msg != "": msg = prefix + "// " + msg self.file.write(msg + "\n") def comment(self, msg): self.file.write("// " + msg + "\n") def start_sprite(self, size, is_real_sprite = False): output_base.BinaryOutputBase.start_sprite(self, size) self.print_decimal(self.sprite_num) self.sprite_num += 1 if not is_real_sprite: self.file.write("* ") self.print_decimal(size) def print_sprite(self, sprite_info): self.start_sprite(1, True) self.file.write(sprite_info.file.value + " ") self.print_decimal(sprite_info.xpos.value) self.print_decimal(sprite_info.ypos.value) self.print_bytex(sprite_info.compression.value) self.print_decimal(sprite_info.ysize.value) self.print_decimal(sprite_info.xsize.value) self.print_decimal(sprite_info.xrel.value) self.print_decimal(sprite_info.yrel.value) self.end_sprite() def print_empty_realsprite(self): self.start_sprite(1) self.print_bytex(0) self.end_sprite() def print_named_filedata(self, filename): self.start_sprite(0, True) self.file.write("** " + filename) self.end_sprite() nml-0.2.4/nml/output_dep.py0000644000061700006170000000235012036626441016363 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- import codecs from nml import generic, grfstrings, output_base class OutputDEP(output_base.OutputBase): """ Class for output to a dependency file in makefile format. """ def __init__(self, filename, grf_filename): output_base.OutputBase.__init__(self, filename) self.grf_filename = grf_filename def open_file(self): return codecs.open(self.filename, 'w', 'utf-8') def write(self, text): self.file.write(self.grf_filename + ': ' + text + '\n') def skip_sprite_checks(self): return True nml-0.2.4/nml/free_number_list.py0000644000061700006170000000634712036626442017532 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class FreeNumberList(object): """ Contains a list with numbers and functions to pop one number from the list, to save the current state and to restore to the previous state. @ivar free_numbers: The list with currently unused numbers. @type free_numbers: C{list} @ivar states: A list of lists. Each sublist contains all numbers that were L{popped} between the last call to L{save} and the next call to L{save}. Every time L{save} is called one sublist is added, every time L{restore} is called the topmost sublist is removed and it's values are added to the free number list again. @type states: C{list} @ivar used_numbers: A set with all numbers that have been used at some time. This is used by L{pop_unique}. @type used_numbers: C{set} """ def __init__(self, free_numbers): self.free_numbers = free_numbers self.states = [] self.used_numbers = set() def pop(self): """ Pop a free number from the list. You have to call L{save} at least once before calling L{pop}. @return: Some free number. """ assert len(self.states) > 0 assert len(self.free_numbers) > 0 num = self.free_numbers.pop() self.states[-1].append(num) self.used_numbers.add(num) return num def pop_global(self): """ Pop a free number from the list. The number may have been used before and already been restored but it'll never be given out again. @return: Some free number. """ assert len(self.free_numbers) > 0 return self.free_numbers.pop() def pop_unique(self): """ Pop a free number from the list. The number has not been used before and will not be used again. @return: A unique free number. """ available = set(self.free_numbers) - self.used_numbers for num in available: self.free_numbers.remove(num) self.used_numbers.add(num) return num assert False, "No unique number available" def save(self): """ Save the current state. All calls to L{pop} will be saved and can be reverted by calling L{restore} """ self.states.append([]) def restore(self): """ Add all numbers given out via L{pop} since the last L{save} to the free number list. """ assert len(self.states) > 0 self.states[-1].reverse() self.free_numbers.extend(self.states[-1]) self.states.pop() nml-0.2.4/nml/ast/0000755000061700006170000000000012036626604014411 5ustar abuildabuild00000000000000nml-0.2.4/nml/ast/basecost.py0000644000061700006170000001653612036626442016601 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.ast import assignment, base_statement from nml.actions import action0 class BaseCost(base_statement.BaseStatement): """ AST Node for a base costs table. @ivar costs: List of base cost values to set. @type costs: C{list} of L{Assignment} """ def __init__(self, costs, pos): base_statement.BaseStatement.__init__(self, "basecost-block", pos) self.costs = costs def pre_process(self): new_costs = [] for cost in self.costs: cost.value = cost.value.reduce(global_constants.const_list) if isinstance(cost.value, expression.ConstantNumeric): generic.check_range(cost.value.value, -8, 16, 'Base cost value', cost.value.pos) cost.value = expression.BinOp(nmlop.ADD, cost.value, expression.ConstantNumeric(8), cost.value.pos).reduce() if isinstance(cost.name, expression.Identifier): if cost.name.value in base_cost_table: cost.name = expression.ConstantNumeric(base_cost_table[cost.name.value][0]) new_costs.append(cost) elif cost.name.value in generic_base_costs: #create temporary list, so it can be sorted for efficiency tmp_list = [] for num, type in base_cost_table.values(): if type == cost.name.value: tmp_list.append(assignment.Assignment(expression.ConstantNumeric(num), cost.value, cost.name.pos)) tmp_list.sort(lambda x, y: cmp(x.name.value, y.name.value)) new_costs.extend(tmp_list) else: raise generic.ScriptError("Unrecognized base cost identifier '%s' encountered" % cost.name.value, cost.name.pos) else: cost.name = cost.name.reduce() if isinstance(cost.name, expression.ConstantNumeric): generic.check_range(cost.name.value, 0, len(base_cost_table), 'Base cost number', cost.name.pos) new_costs.append(cost) self.costs = new_costs def debug_print(self, indentation): print indentation*' ' + 'Base costs' for cost in self.costs: cost.debug_print(indentation + 2) def get_action_list(self): return action0.get_basecost_action(self) def __str__(self): ret = "basecost {\n" for cost in self.costs: ret += "\t%s: %s;\n" % (str(cost.name), str(cost.value)) ret += "}\n" return ret base_cost_table = { 'PR_STATION_VALUE' : (0, ''), 'PR_BUILD_RAIL' : (1, 'PR_CONSTRUCTION'), 'PR_BUILD_ROAD' : (2, 'PR_CONSTRUCTION'), 'PR_BUILD_SIGNALS' : (3, 'PR_CONSTRUCTION'), 'PR_BUILD_BRIDGE' : (4, 'PR_CONSTRUCTION'), 'PR_BUILD_DEPOT_TRAIN' : (5, 'PR_CONSTRUCTION'), 'PR_BUILD_DEPOT_ROAD' : (6, 'PR_CONSTRUCTION'), 'PR_BUILD_DEPOT_SHIP' : (7, 'PR_CONSTRUCTION'), 'PR_BUILD_TUNNEL' : (8, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_RAIL' : (9, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_RAIL_LENGTH' : (10, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_AIRPORT' : (11, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_BUS' : (12, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_TRUCK' : (13, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_DOCK' : (14, 'PR_CONSTRUCTION'), 'PR_BUILD_VEHICLE_TRAIN' : (15, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_WAGON' : (16, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_AIRCRAFT' : (17, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_ROAD' : (18, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_SHIP' : (19, 'PR_BUILD_VEHICLE'), 'PR_BUILD_TREES' : (20, 'PR_CONSTRUCTION'), 'PR_TERRAFORM' : (21, 'PR_CONSTRUCTION'), 'PR_CLEAR_GRASS' : (22, 'PR_CONSTRUCTION'), 'PR_CLEAR_ROUGH' : (23, 'PR_CONSTRUCTION'), 'PR_CLEAR_ROCKS' : (24, 'PR_CONSTRUCTION'), 'PR_CLEAR_FIELDS' : (25, 'PR_CONSTRUCTION'), 'PR_CLEAR_TREES' : (26, 'PR_CONSTRUCTION'), 'PR_CLEAR_RAIL' : (27, 'PR_CONSTRUCTION'), 'PR_CLEAR_SIGNALS' : (28, 'PR_CONSTRUCTION'), 'PR_CLEAR_BRIDGE' : (29, 'PR_CONSTRUCTION'), 'PR_CLEAR_DEPOT_TRAIN' : (30, 'PR_CONSTRUCTION'), 'PR_CLEAR_DEPOT_ROAD' : (31, 'PR_CONSTRUCTION'), 'PR_CLEAR_DEPOT_SHIP' : (32, 'PR_CONSTRUCTION'), 'PR_CLEAR_TUNNEL' : (33, 'PR_CONSTRUCTION'), 'PR_CLEAR_WATER' : (34, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_RAIL' : (35, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_AIRPORT' : (36, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_BUS' : (37, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_TRUCK' : (38, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_DOCK' : (39, 'PR_CONSTRUCTION'), 'PR_CLEAR_HOUSE' : (40, 'PR_CONSTRUCTION'), 'PR_CLEAR_ROAD' : (41, 'PR_CONSTRUCTION'), 'PR_RUNNING_TRAIN_STEAM' : (42, 'PR_RUNNING'), 'PR_RUNNING_TRAIN_DIESEL' : (43, 'PR_RUNNING'), 'PR_RUNNING_TRAIN_ELECTRIC' : (44, 'PR_RUNNING'), 'PR_RUNNING_AIRCRAFT' : (45, 'PR_RUNNING'), 'PR_RUNNING_ROADVEH' : (46, 'PR_RUNNING'), 'PR_RUNNING_SHIP' : (47, 'PR_RUNNING'), 'PR_BUILD_INDUSTRY' : (48, 'PR_CONSTRUCTION'), 'PR_CLEAR_INDUSTRY' : (49, 'PR_CONSTRUCTION'), 'PR_BUILD_UNMOVABLE' : (50, 'PR_CONSTRUCTION'), 'PR_CLEAR_UNMOVABLE' : (51, 'PR_CONSTRUCTION'), 'PR_BUILD_WAYPOINT_RAIL' : (52, 'PR_CONSTRUCTION'), 'PR_CLEAR_WAYPOINT_RAIL' : (53, 'PR_CONSTRUCTION'), 'PR_BUILD_WAYPOINT_BUOY' : (54, 'PR_CONSTRUCTION'), 'PR_CLEAR_WAYPOINT_BUOY' : (55, 'PR_CONSTRUCTION'), 'PR_TOWN_ACTION' : (56, 'PR_CONSTRUCTION'), 'PR_BUILD_FOUNDATION' : (57, 'PR_CONSTRUCTION'), 'PR_BUILD_INDUSTRY_RAW' : (58, 'PR_CONSTRUCTION'), 'PR_BUILD_TOWN' : (59, 'PR_CONSTRUCTION'), 'PR_BUILD_CANAL' : (60, 'PR_CONSTRUCTION'), 'PR_CLEAR_CANAL' : (61, 'PR_CONSTRUCTION'), 'PR_BUILD_AQUEDUCT' : (62, 'PR_CONSTRUCTION'), 'PR_CLEAR_AQUEDUCT' : (63, 'PR_CONSTRUCTION'), 'PR_BUILD_LOCK' : (64, 'PR_CONSTRUCTION'), 'PR_CLEAR_LOCK' : (65, 'PR_CONSTRUCTION'), } generic_base_costs = ['PR_CONSTRUCTION', 'PR_RUNNING', 'PR_BUILD_VEHICLE'] nml-0.2.4/nml/ast/skipall.py0000644000061700006170000000222212036626441016417 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action7 from nml.ast import base_statement class SkipAll(base_statement.BaseStatement): """ Skip everything after this statement. """ def __init__(self, pos): base_statement.BaseStatement.__init__(self, "exit-statement", pos) def get_action_list(self): return [action7.UnconditionalSkipAction(9, 0)] def debug_print(self, indentation): print indentation*' ' + 'Skip all' def __str__(self): return "exit;\n" nml-0.2.4/nml/ast/switch.py0000644000061700006170000003140412036626442016266 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action2, action2var, action2random from nml.ast import base_statement, general var_ranges = { 'SELF' : 0x89, 'PARENT' : 0x8A } # Used by Switch and RandomSwitch switch_base_class = action2.make_sprite_group_class(False, True, True) class Switch(switch_base_class): def __init__(self, param_list, body, pos): base_statement.BaseStatement.__init__(self, "switch-block", pos, False, False) if len(param_list) != 4: raise generic.ScriptError("Switch-block requires 4 parameters, encountered " + str(len(param_list)), pos) if not isinstance(param_list[1], expression.Identifier): raise generic.ScriptError("Switch-block parameter 2 'variable range' must be an identifier.", param_list[1].pos) if param_list[1].value in var_ranges: self.var_range = var_ranges[param_list[1].value] else: raise generic.ScriptError("Unrecognized value for switch parameter 2 'variable range': '%s'" % param_list[1].value, param_list[1].pos) if not isinstance(param_list[2], expression.Identifier): raise generic.ScriptError("Switch-block parameter 3 'name' must be an identifier.", param_list[2].pos) self.initialize(param_list[2], general.parse_feature(param_list[0]).value) self.expr = param_list[3] self.body = body def pre_process(self): var_feature = action2var.get_feature(self) # Feature of the accessed variables self.expr = action2var.reduce_varaction2_expr(self.expr, var_feature) self.body.reduce_expressions(var_feature) switch_base_class.pre_process(self) def collect_references(self): all_refs = [] for result in [r.result for r in self.body.ranges] + [self.body.default]: if isinstance(result, expression.SpriteGroupRef): all_refs.append(result) return all_refs def debug_print(self, indentation): print indentation*' ' + 'Switch, Feature = %d, name = %s', (self.feature_set.copy().pop(), self.name.value) print (2+indentation)*' ' + 'Expression:' self.expr.debug_print(indentation + 4) print (2+indentation)*' ' + 'Body:' self.body.debug_print(indentation + 4) def get_action_list(self): if self.prepare_output(): return action2var.parse_varaction2(self) return [] def __str__(self): var_range = 'SELF' if self.var_range == 0x89 else 'PARENT' return 'switch(%s, %s, %s, %s) {\n%s}\n' % (str(self.feature_set.copy().pop()), var_range, str(self.name), str(self.expr), str(self.body)) class SwitchBody(object): """ AST-node representing the body of a switch block This contains the various ranges as well as the default value @ivar ranges: List of ranges @type ranges: C{list} of L{SwitchRange} @ivar default: Default result to use if no range matches @type default: L{SpriteGroupRef}, L{Expression} or C{None} (before pre-processing only), depending on the type of result. """ def __init__(self, ranges, default): self.ranges = ranges self.default = default def reduce_expressions(self, var_feature): if self.default is not None: self.default = action2var.reduce_varaction2_expr(self.default, var_feature) for r in self.ranges: r.reduce_expressions(var_feature) def debug_print(self, indentation): for r in self.ranges: r.debug_print(indentation) print indentation*' ' + 'Default:' if self.default is not None: self.default.debug_print(indentation + 2) else: print (indentation+2)*' ' + 'Return computed value' def __str__(self): ret = '' for r in self.ranges: ret += '\t%s\n' % str(r) if self.default is None: ret += '\treturn;\n' elif isinstance(self.default, expression.SpriteGroupRef): ret += '\t%s;\n' % str(self.default) else: ret += '\treturn %s;\n' % str(self.default) return ret class SwitchRange(object): def __init__(self, min, max, result, unit = None): self.min = min self.max = max self.result = result self.unit = unit def reduce_expressions(self, var_feature): self.min = self.min.reduce(global_constants.const_list) self.max = self.max.reduce(global_constants.const_list) if self.result is not None: self.result = action2var.reduce_varaction2_expr(self.result, var_feature) def debug_print(self, indentation): print indentation*' ' + 'Min:' self.min.debug_print(indentation + 2) print indentation*' ' + 'Max:' self.max.debug_print(indentation + 2) print indentation*' ' + 'Result:' if self.result is not None: self.result.debug_print(indentation + 2) else: print (indentation+2)*' ' + 'Return computed value' def __str__(self): ret = str(self.min) if not isinstance(self.min, expression.ConstantNumeric) or not isinstance(self.max, expression.ConstantNumeric) or self.max.value != self.min.value: ret += '..' + str(self.max) if self.result is None: ret += ': return;' elif isinstance(self.result, expression.SpriteGroupRef): ret += ': %s;' % str(self.result) else: ret += ': return %s;' % str(self.result) return ret class RandomSwitch(switch_base_class): def __init__(self, param_list, choices, pos): base_statement.BaseStatement.__init__(self, "random_switch-block", pos, False, False) if not (3 <= len(param_list) <= 4): raise generic.ScriptError("random_switch requires 3 or 4 parameters, encountered %d" % len(param_list), pos) #feature feature = general.parse_feature(param_list[0]).value #type self.type = param_list[1] # Extract type name and possible argument if isinstance(self.type, expression.Identifier): self.type_count = None elif isinstance(self.type, expression.FunctionCall): if len(self.type.params) == 0: self.type_count = None elif len(self.type.params) == 1: self.type_count = action2var.reduce_varaction2_expr(self.type.params[0], feature) else: raise generic.ScriptError("Value for random_switch parameter 2 'type' can have only one parameter.", self.type.pos) self.type = self.type.name else: raise generic.ScriptError("random_switch parameter 2 'type' should be an identifier, possibly with a parameter.", self.type.pos) #name if not isinstance(param_list[2], expression.Identifier): raise generic.ScriptError("random_switch parameter 3 'name' should be an identifier", pos) name = param_list[2] #triggers self.triggers = param_list[3] if len(param_list) == 4 else expression.ConstantNumeric(0) #body self.choices = [] self.dependent = [] self.independent = [] for choice in choices: if isinstance(choice.probability, expression.Identifier): if choice.probability.value == 'dependent': self.dependent.append(choice.result) continue elif choice.probability.value == 'independent': self.independent.append(choice.result) continue self.choices.append(choice) if len(self.choices) == 0: raise generic.ScriptError("random_switch requires at least one possible choice", pos) self.initialize(name, feature) self.random_act2 = None # Set during action generation to resolve dependent/independent chains def pre_process(self): for choice in self.choices: choice.reduce_expressions(self.feature_set.copy().pop()) for dep_list in (self.dependent, self.independent): for i, dep in enumerate(dep_list[:]): dep_list[i] = dep.reduce(global_constants.const_list) # Make sure, all [in]dependencies refer to existing random switch blocks if (not isinstance(dep_list[i], expression.SpriteGroupRef)) or len(dep_list[i].param_list) > 0: raise generic.ScriptError("Value for (in)dependent should be an identifier", dep_list[i].pos) spritegroup = action2.resolve_spritegroup(dep_list[i].name) if not isinstance(spritegroup, RandomSwitch): raise generic.ScriptError("Value of (in)dependent '%s' should refer to a random_switch." % dep_list[i].name.value, dep_list[i].pos) self.triggers = self.triggers.reduce_constant(global_constants.const_list) if not (0 <= self.triggers.value <= 255): raise generic.ScriptError("random_switch parameter 4 'triggers' out of range 0..255, encountered " + str(self.triggers.value), self.triggers.pos) switch_base_class.pre_process(self) def collect_references(self): all_refs = [] for choice in self.choices: if isinstance(choice.result, expression.SpriteGroupRef): all_refs.append(choice.result) return all_refs def debug_print(self, indentation): print indentation*' ' + 'Random' print (2+indentation)*' ' + 'Feature:', self.feature_set.copy().pop() print (2+indentation)*' ' + 'Type:' self.type.debug_print(indentation + 4) print (2+indentation)*' ' + 'Name:', self.name.value print (2+indentation)*' ' + 'Triggers:' self.triggers.debug_print(indentation + 4) for dep in self.dependent: print (2+indentation)*' ' + 'Dependent on:' dep.debug_print(indentation + 4) for indep in self.independent: print (2+indentation)*' ' + 'Independent from:' indep.debug_print(indentation + 4) print (2+indentation)*' ' + 'Choices:' for choice in self.choices: choice.debug_print(indentation + 4) def get_action_list(self): if self.prepare_output(): return action2random.parse_randomswitch(self) return [] def __str__(self): ret = 'random_switch(%s, %s, %s, %s) {\n' % (str(self.feature_set.copy().pop()), str(self.type), str(self.name), str(self.triggers)) for dep in self.dependent: ret += 'dependent: %s;\n' % str(dep) for indep in self.independent: ret += 'independent: %s;\n' % str(indep) for choice in self.choices: ret += str(choice) + '\n' ret += '}\n' return ret class RandomChoice(object): """ Class to hold one of the possible choices in a random_switch @ivar probability: Relative chance for this choice to be chosen @type probability: L{Expression} @ivar result: Result of this choice, either another action2 or a return value @type result: L{SpriteGroupRef} or L{Expression} """ def __init__ (self, probability, result): self.probability = probability if result is None: raise generic.ScriptError("Returning the computed value is not possible in a random_switch, as there is no computed value.", self.probability.pos) self.result = result def reduce_expressions(self, var_feature): self.probability = self.probability.reduce_constant(global_constants.const_list) if self.probability.value <= 0: raise generic.ScriptError("Random probability must be higher than 0", self.probability.pos) self.result = action2var.reduce_varaction2_expr(self.result, var_feature) def debug_print(self, indentation): print indentation*' ' + 'Probability:' self.probability.debug_print(indentation + 2) print indentation*' ' + 'Result:' self.result.debug_print(indentation + 2) def __str__(self): ret = str(self.probability) if isinstance(self.result, expression.SpriteGroupRef): ret += ': %s;' % str(self.result) else: ret += ': return %s;' % str(self.result) return ret nml-0.2.4/nml/ast/item.py0000644000061700006170000002565312036626442015734 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, unit from nml.ast import base_statement, general from nml.actions import action0, action2, action2var, action3 item_feature = None item_id = None class Item(base_statement.BaseStatementList): """ AST-node representing an item block @ivar feature: Feature of the item @type feature: L{ConstantNumeric} @ivar name: Name of the item @type name: L{Identifier} or C{None} if N/A. @ivar id: Numeric ID of the item @type id: C{int} """ def __init__(self, params, body, pos): base_statement.BaseStatementList.__init__(self, "item-block", pos, base_statement.BaseStatementList.LIST_TYPE_ITEM, body) if len(params) >= 1: self.feature = general.parse_feature(params[0]) else: raise generic.ScriptError("Item block requires at least one parameter, got 0", self.pos) if len(params) > 3: raise generic.ScriptError("Item block requires at most 3 parameters, found %d" % len(params), self.pos) self.id = params[2] if len(params) == 3 else None self.name = params[1] if len(params) >= 2 else None def register_names(self): if self.id: self.id = self.id.reduce(global_constants.const_list) if self.name: if not isinstance(self.name, expression.Identifier): raise generic.ScriptError("Item parameter 2 'name' should be an identifier", self.pos) if self.name.value in global_constants.item_names: existing_id = global_constants.item_names[self.name.value].id if not isinstance(existing_id, expression.ConstantNumeric): raise generic.ScriptError("Item with name '%s' has already been assigned a non-constant ID, extending this item definition is not possible." % self.name.value, self.pos) if self.id is not None and (not isinstance(self.id, expression.ConstantNumeric) or existing_id.value != self.id.value): raise generic.ScriptError("Item with name '%s' has already been assigned to id %d, cannot reassign to id %d" % (self.name.value, existing_id.value, self.id.value), self.pos) self.id = existing_id if self.id is None: self.id = expression.ConstantNumeric(action0.get_free_id(self.feature.value)) if self.name is not None: global_constants.item_names[self.name.value] = self base_statement.BaseStatementList.register_names(self) def pre_process(self): global item_feature, item_id item_id = self.id item_feature = self.feature.value base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): print indentation*' ' + 'Item, feature', hex(self.feature.value) base_statement.BaseStatementList.debug_print(self, indentation + 2) def get_action_list(self): global item_feature, item_id item_id = self.id item_feature = self.feature.value return base_statement.BaseStatementList.get_action_list(self) def __str__(self): ret = 'item(%d' % self.feature.value if self.name is not None: ret += ', %s' % str(self.name) if self.id is not None: ret += ', %s' % str(self.id) ret += ') {\n' ret += base_statement.BaseStatementList.__str__(self) ret += '}\n' return ret class Unit(object): def __init__(self, name): assert name in unit.units self.name = name self.type = unit.units[name]['type'] self.convert = unit.units[name]['convert'] self.ottd_mul = unit.units[name]['ottd_mul'] self.ottd_shift = unit.units[name]['ottd_shift'] def __str__(self): return self.name class Property(object): """ AST-node representing a single property. These are only valid insde a PropertyBlock. @ivar name: The name (or number) of this property. @type name: L{Identifier} or L{ConstantNumeric}. @ivar value: The value that will be assigned to this property. @type value: L{Expression}. @ivar unit: The unit of the value. @type unit: L{Unit} @ivar pos: Position information. @type pos: L{Position} """ def __init__(self, name, value, unit, pos): self.pos = pos self.name = name self.value = value self.unit = unit def pre_process(self): self.value = self.value.reduce(global_constants.const_list, unknown_id_fatal = False) if (self.unit is not None and self.unit.type != 'nfo') and not (isinstance(self.value, expression.ConstantNumeric) or isinstance(self.value, expression.ConstantFloat)): raise generic.ScriptError("Using a unit for a property is only allowed if the value is constant", self.pos) def debug_print(self, indentation): print indentation*' ' + 'Property:', self.name.value self.value.debug_print(indentation + 2) def __str__(self): unit = '' if self.unit is None else ' ' + str(self.unit) return '\t%s: %s%s;' % (self.name, self.value, unit) class PropertyBlock(base_statement.BaseStatement): """ Block that contains a list of property/value pairs to be assigned to the current item. @ivar prop_list: List of properties. @type prop_list: C{list} of L{Property} """ def __init__(self, prop_list, pos): base_statement.BaseStatement.__init__(self, "property-block", pos, in_item = True, out_item = False) self.prop_list = prop_list def pre_process(self): for prop in self.prop_list: prop.pre_process() def debug_print(self, indentation): print indentation*' ' + 'Property block:' for prop in self.prop_list: prop.debug_print(indentation + 2) def get_action_list(self): return action0.parse_property_block(self.prop_list, item_feature, item_id) def __str__(self): ret = 'property {\n' for prop in self.prop_list: ret += '%s\n' % str(prop) ret += '}\n' return ret class LiveryOverride(base_statement.BaseStatement): def __init__(self, wagon_id, graphics_block, pos): base_statement.BaseStatement.__init__(self, "livery override", pos, in_item = True, out_item = False) self.graphics_block = graphics_block self.wagon_id = wagon_id def pre_process(self): self.graphics_block.pre_process() pass def debug_print(self, indentation): print indentation*' ' + 'Liverry override, wagon id:' self.wagon_id.debug_print(indentation + 2) for graphics in self.graphics_block.graphics_list: graphics.debug_print(indentation + 2) print (indentation+2)*' ' + 'Default graphics:', self.graphics_block.default_graphics def get_action_list(self): wagon_id = self.wagon_id.reduce_constant([(global_constants.item_names, global_constants.item_to_id)]) return action3.parse_graphics_block(self.graphics_block, item_feature, wagon_id, True) def __str__(self): ret = 'livery_override(%s) {\n' % str(self.wagon_id) for graphics in self.graphics_block.graphics_list: ret += "\t%s\n" % str(graphics) if self.graphics_block.default_graphics is not None: ret += '\t%s;\n' % str(self.graphics_block.default_graphics) ret += '}\n' return ret graphics_base_class = action2.make_sprite_group_class(False, False, True) class GraphicsBlock(graphics_base_class): def __init__(self, graphics_list, default_graphics, pos): base_statement.BaseStatement.__init__(self, "graphics-block", pos, in_item = True, out_item = False) self.graphics_list = graphics_list self.default_graphics = default_graphics def pre_process(self): for graphics_def in self.graphics_list: graphics_def.reduce_expressions(item_feature) if self.default_graphics is not None: self.default_graphics = action2var.reduce_varaction2_expr(self.default_graphics, item_feature) # initialize base class and pre_process it as well (in that order) self.initialize(None, item_feature) graphics_base_class.pre_process(self) def collect_references(self): all_refs = [] for result in [g.result for g in self.graphics_list] + [self.default_graphics]: if isinstance(result, expression.SpriteGroupRef): # Default may be None all_refs.append(result) return all_refs def debug_print(self, indentation): print indentation*' ' + 'Graphics block:' for graphics in self.graphics_list: graphics.debug_print(indentation + 2) if self.default_graphics is not None: print (indentation+2)*' ' + 'Default graphics:' self.default_graphics.debug_print(indentation + 4) def get_action_list(self): if self.prepare_output(): return action3.parse_graphics_block(self, item_feature, item_id) return [] def __str__(self): ret = 'graphics {\n' for graphics in self.graphics_list: ret += "\t%s\n" % str(graphics) if self.default_graphics is not None: ret += '\t%s;\n' % str(self.default_graphics) ret += '}\n' return ret class GraphicsDefinition(object): def __init__(self, cargo_id, result, unit = None): self.cargo_id = cargo_id self.result = result self.unit = unit def reduce_expressions(self, var_feature): # Do not reduce cargo-id (yet) if self.result is None: raise generic.ScriptError("Returning the computed value is not possible in a graphics-block, as there is no computed value.", self.cargo_id.pos) self.result = action2var.reduce_varaction2_expr(self.result, var_feature) def debug_print(self, indentation): print indentation*' ' + 'Cargo ID:' self.cargo_id.debug_print(indentation + 2) print indentation*' ' + 'Result:' if self.result is not None: self.result.debug_print(indentation + 2) def __str__(self): ret = str(self.cargo_id) if self.result is None: ret += ': return;' elif isinstance(self.result, expression.SpriteGroupRef): ret += ': %s;' % str(self.result) else: ret += ': return %s;' % str(self.result) return ret nml-0.2.4/nml/ast/disable_item.py0000644000061700006170000000537612036626441017416 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action0 from nml.ast import base_statement, general class DisableItem(base_statement.BaseStatement): """ Class representing a 'disable_item' statement in the AST. @ivar feature: Feature of the items to disable @type feature: L{ConstantNumeric} @ivar first_id: First item ID to disable @type first_id: L{ConstantNumeric}, or C{None} if not set @ivar last_id: Last item ID to disable @type last_id: L{ConstantNumeric}, or C{None} if not set """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "disable_item()", pos) if not (1 <= len(param_list) <= 3): raise generic.ScriptError("disable_item() requires between 1 and 3 parameters, encountered %d." % len(param_list), pos) self.feature = general.parse_feature(param_list[0]) if len(param_list) > 1: self.first_id = param_list[1].reduce_constant(global_constants.const_list) else: self.first_id = None if len(param_list) > 2: self.last_id = param_list[2].reduce_constant(global_constants.const_list) if self.last_id.value < self.first_id.value: raise generic.ScriptError("Last id to disable may not be lower than the first id.", pos) else: self.last_id = None def debug_print(self, indentation): print indentation*' ' + 'Disable items, feature=' + str(self.feature.value) if self.first_id is not None: print (indentation+2)*' ' + 'First ID:' self.first_id.debug_print(indentation + 4) if self.last_id is not None: print (indentation+2)*' ' + 'Last ID:' self.last_id.debug_print(indentation + 4) def __str__(self): ret = str(self.feature) if self.first_id is not None: ret += ', ' + str(self.first_id) if self.last_id is not None: ret += ', ' + str(self.last_id) return 'disable_item(%s);\n' % ret def get_action_list(self): return action0.get_disable_actions(self) nml-0.2.4/nml/ast/error.py0000644000061700006170000000756112036626441016124 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression from nml.actions import actionB from nml.ast import base_statement class Error(base_statement.BaseStatement): """ An error has occured while parsing the GRF. This can be anything ranging from an imcompatible GRF file that was found or a game setting that is set to the wrong value to a wrong combination of parameters. The action taken by the host depends on the severity level of the error. NML equivalent: error(level, message[, extra_text[, parameter1[, parameter2]]]). @ivar params: Extra expressions whose value can be used in the error string. @type params: C{list} of L{Expression} @ivar severity: Severity level of this error, value between 0 and 3. @type severity: L{Expression} @ivar msg: The string to be used for this error message. This can be either one of the predifined error strings or a custom string from the language file. @type msg: L{Expression} @ivar data: Optional extra message that is inserted in place of the second {STRING}-code of msg. @type data: C{None} or L{String} or L{StringLiteral} """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "error()", pos) if not 2 <= len(param_list) <= 5: raise generic.ScriptError("'error' expects between 2 and 5 parameters, got " + str(len(param_list)), self.pos) self.severity = param_list[0] self.msg = param_list[1] self.data = param_list[2] if len(param_list) >= 3 else None self.params = param_list[3:] def pre_process(self): self.severity = self.severity.reduce([actionB.error_severity]) self.msg = self.msg.reduce([actionB.default_error_msg]) if self.data: self.data = self.data.reduce() self.params = [x.reduce() for x in self.params] def debug_print(self, indentation): print indentation*' ' + 'Error message' print (indentation+2)*' ' + 'Message:' self.msg.debug_print(indentation + 4) print (indentation+2)*' ' + 'Severity:' self.severity.debug_print(indentation + 4) print (indentation+2)*' ' + 'Data: ' if self.data is not None: self.data.debug_print(indentation + 4) if len(self.params) > 0: print (indentation+2)*' ' + 'Param1: ' self.params[0].debug_print(indentation + 4) if len(self.params) > 1: print (indentation+2)*' ' + 'Param2: ' self.params[1].debug_print(indentation + 4) def get_action_list(self): return actionB.parse_error_block(self) def __str__(self): sev = str(self.severity) if isinstance(self.severity, expression.ConstantNumeric): for s in actionB.error_severity: if self.severity.value == actionB.error_severity[s]: sev = s break res = 'error(%s, %s' % (sev, self.msg) if self.data is not None: res += ', %s' % self.data if len(self.params) > 0: res += ', %s' % self.params[0] if len(self.params) > 1: res += ', %s' % self.params[1] res += ');\n' return res nml-0.2.4/nml/ast/sort_vehicles.py0000644000061700006170000000431512036626441017636 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.ast import base_statement, general from nml.actions import action0 class SortVehicles(base_statement.BaseStatement): """ AST-node representing a sort-vehicles block. @ivar feature: Feature of the item @type feature: L{ConstantNumeric} @ivar vehid_list: List of vehicle ids. @type vehid_list: L{Array}. """ def __init__(self, params, pos): base_statement.BaseStatement.__init__(self, "sort-block", pos) if len(params) != 2: raise generic.ScriptError("Sort-block requires exactly two parameters, got %d" % len(params), self.pos) self.feature = general.parse_feature(params[0]) self.vehid_list = params[1] def pre_process(self): self.vehid_list = self.vehid_list.reduce(global_constants.const_list) if not isinstance(self.vehid_list, expression.Array) or not all([isinstance(x, expression.ConstantNumeric) for x in self.vehid_list.values]): raise generic.ScriptError("Second parameter is not an array of one of the items in it could not be reduced to a constnat numer", self.pos) def debug_print(self, indentation): print indentation*' ' + 'Sort, feature', hex(self.feature.value) for id in self.vehid_list.values: print (indentation+2)*' ' + 'Vehicle id:', id def get_action_list(self): return action0.parse_sort_block(self.feature.value, self.vehid_list.values) def __str__(self): return 'sort(%d, %s);\n' % (self.feature.value, self.vehid_list) nml-0.2.4/nml/ast/loop.py0000644000061700006170000000355612036626441015744 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action7 from nml.ast import base_statement from nml import global_constants class Loop(base_statement.BaseStatementList): """ AST node for a while-loop. @ivar expr: The conditional to check whether the loop continues. @type expr: L{Expression} """ def __init__(self, expr, block, pos): base_statement.BaseStatementList.__init__(self, "while-loop", pos, base_statement.BaseStatementList.LIST_TYPE_LOOP, block, in_item = True) self.expr = expr def pre_process(self): self.expr = self.expr.reduce(global_constants.const_list) base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): print indentation*' ' + 'While loop' print (2+indentation)*' ' + 'Expression:' self.expr.debug_print(indentation + 4) print (2+indentation)*' ' + 'Block:' base_statement.BaseStatementList.debug_print(self, indentation + 4) def get_action_list(self): return action7.parse_loop_block(self) def __str__(self): ret = 'while(%s) {\n' % self.expr ret += base_statement.BaseStatementList.__str__(self) ret += '}\n' return ret nml-0.2.4/nml/ast/cargotable.py0000644000061700006170000000351612036626441017072 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression from nml.actions import action0 from nml.ast import base_statement class CargoTable(base_statement.BaseStatement): def __init__(self, cargo_list, pos): base_statement.BaseStatement.__init__(self, "cargo table", pos, False, False) self.cargo_list = cargo_list generic.OnlyOnce.enforce(self, "cargo table") for i, cargo in enumerate(cargo_list): if isinstance(cargo, expression.Identifier): self.cargo_list[i] = expression.StringLiteral(cargo.value, cargo.pos) expression.parse_string_to_dword(self.cargo_list[i]) global_constants.cargo_numbers[self.cargo_list[i].value] = i def debug_print(self, indentation): print indentation*' ' + 'Cargo table' for cargo in self.cargo_list: print (indentation+2)*' ' + 'Cargo:', cargo.value def get_action_list(self): return action0.get_cargolist_action(self.cargo_list) def __str__(self): ret = 'cargotable {\n' ret += ', '.join([expression.identifier_to_print(cargo.value) for cargo in self.cargo_list]) ret += '\n}\n' return ret nml-0.2.4/nml/ast/deactivate.py0000644000061700006170000000300512036626441017071 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import actionE from nml.ast import base_statement from nml import expression class DeactivateBlock(base_statement.BaseStatement): def __init__(self, grfid_list, pos): base_statement.BaseStatement.__init__(self, "deactivate()", pos) self.grfid_list = grfid_list def pre_process(self): # Parse (string-)expressions to integers self.grfid_list = [expression.parse_string_to_dword(grfid.reduce()) for grfid in self.grfid_list] def debug_print(self, indentation): print indentation*' ' + 'Deactivate other newgrfs:' for grfid in self.grfid_list: grfid.debug_print(indentation + 2) def get_action_list(self): return actionE.parse_deactivate_block(self) def __str__(self): return 'deactivate(%s);\n' % ', '.join([str(grfid) for grfid in self.grfid_list]) nml-0.2.4/nml/ast/produce.py0000644000061700006170000000607212036626442016431 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.actions import action2, action2var, action2production from nml.ast import base_statement produce_base_class = action2.make_sprite_group_class(False, True, True) class Produce(produce_base_class): """ AST node for a 'produce'-block, which is basically equivalent to the production callback. Syntax: produce(name, sub1, sub2, sub3, add1, add2[, again]) @ivar param_list: List of parameters supplied to the produce-block. - 0..2: Amounts of cargo to subtract from input - 3..4: Amounts of cargo to add to output - 5: Run the production CB again if nonzero @type param_list: C{list} of L{Expression} """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "produce-block", pos, False, False) if not (6 <= len(param_list) <= 7): raise generic.ScriptError("produce-block requires 6 or 7 parameters, encountered %d" % len(param_list), self.pos) name = param_list[0] if not isinstance(name, expression.Identifier): raise generic.ScriptError("produce parameter 1 'name' should be an identifier.", name.pos) self.initialize(name, 0x0A) self.param_list = param_list[1:] if len(self.param_list) < 6: self.param_list.append(expression.ConstantNumeric(0)) def pre_process(self): for i, param in enumerate(self.param_list): self.param_list[i] = action2var.reduce_varaction2_expr(param, 0x0A) produce_base_class.pre_process(self) def collect_references(self): return [] def __str__(self): return 'produce(%s);\n' % ', '.join(str(x) for x in [self.name] + self.param_list) def debug_print(self, indentation): print indentation*' ' + 'Produce, name =', str(self.name) print (indentation+2)*' ' + 'Subtract from input:' for expr in self.param_list[0:3]: expr.debug_print(indentation + 4) print (indentation+2)*' ' + 'Add to output:' for expr in self.param_list[3:5]: expr.debug_print(indentation + 4) print (indentation+2)*' ' + 'Again:' self.param_list[5].debug_print(indentation + 4) def get_action_list(self): if self.prepare_output(): return action2production.get_production_actions(self) return [] nml-0.2.4/nml/ast/townnames.py0000644000061700006170000003725512036626441017011 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import heapq from nml import expression, generic, grfstrings from nml.actions import actionF from nml.ast import base_statement townname_serial = 1 class TownNames(base_statement.BaseStatement): """ 'town_names' ast node. @ivar name: Name ID of the town_name. @type name: C{None}, L{Identifier}, or L{ConstantNumeric} @ivar id_number: Allocated ID number for this town_name action F node. @type id_number: C{None} or C{int} @ivar style_name: Name of the translated string containing the name of the style, if any. @type style_name: C{None} or L{String} @ivar actFs: Action F instance needed before this one. @type actFs: C{list} of L{ActionF} @ivar parts: Parts of the names. @type parts: C{list} of L{TownNamesPart} @ivar param_list: Stored parameter list. @type param_list: C{list} of (L{TownNamesPart} or L{TownNamesParam}) """ def __init__(self, name, param_list, pos): base_statement.BaseStatement.__init__(self, "town_names-block", pos, False, False) self.name = name self.param_list = param_list self.id_number = None self.style_name = None self.actFs = [] self.parts = [] def debug_print(self, indentation): if isinstance(self.name, basestring): name_text = "name = " + repr(self.name) if self.id_number is not None: name_text += " (allocated number is 0x%x)" % self.id_number elif self.id_number is not None: name_text = "number = 0x%x" % self.id_number else: name_text = "(unnamed)" print indentation*' ' + 'Town name ' + name_text if self.style_name is not None: print indentation*' ' + " style name string:", self.style_name for part in self.parts: print indentation*' ' + "-name part:" part.debug_print(indentation + 2) def pre_process(self): self.actFs = [] self.parts = [] for param in self.param_list: if isinstance(param, TownNamesPart): actFs, part = param.make_actions() self.actFs.extend(actFs) self.parts.append(part) else: if param.key.value != 'styles': raise generic.ScriptError("Expected 'styles' keyword.", param.pos) if len(param.value.params) > 0: raise generic.ScriptError("Parameters of the 'styles' were not expected.", param.pos) if self.style_name is not None: raise generic.ScriptError("'styles' is already defined.", self.pos) self.style_name = param.value if len(self.parts) == 0: raise generic.ScriptError("Missing name parts in a town_names item.", self.pos) # 'name' is actually a number. # Allocate it now, before the self.prepare_output() call (to prevent names to grab it). if self.name is not None and not isinstance(self.name, expression.Identifier): value = self.name.reduce_constant() if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("ID should be an integer number.", self.pos) self.id_number = value.value if self.id_number < 0 or self.id_number > 0x7f: raise generic.ScriptError("ID must be a number between 0 and 0x7f (inclusive)", self.pos) if self.id_number not in actionF.free_numbers: raise generic.ScriptError("town names ID 0x%x is already used." % self.id_number, self.pos) actionF.free_numbers.remove(self.id_number) def __str__(self): ret = 'town_names' if self.name is not None: ret += '(%s)' % str(self.name) ret += '{\n%s}\n' % ''.join(str(x) for x in self.param_list) return ret def get_action_list(self): return self.actFs + [actionF.ActionF(self.name, self.id_number, self.style_name, self.parts, self.pos)] class TownNamesPart(object): """ A class containing a town name part. @ivar pieces: Pieces of the town name part. @type pieces: C{list} of (L{TownNamesEntryDefinition} or L{TownNamesEntryText}) @ivar pos: Position information of the parts block. @type pos: L{Position} @ivar startbit: First bit to use for this part, if defined. @type startbit: C{int} or C{None} @ivar num_bits: Number of bits to use, if defined. @type num_bits: C{int} or C{None} """ def __init__(self, pieces, pos): self.pos = pos self.pieces = pieces self.startbit = None self.num_bits = None def make_actions(self): """ Construct new actionF instances to store all pieces of this part, if needed. @return: Action F that should be defined before, and the processed part. @rtype: C{list} of L{ActionF}, L{TownNamesPart} """ new_pieces = [] for piece in self.pieces: piece.pre_process() if piece.probability.value == 0: generic.print_warning("Dropping town name piece with 0 probability.", piece.pos) else: new_pieces.append(piece) self.pieces = new_pieces actFs = self.move_pieces() if len(self.pieces) == 0: raise generic.ScriptError("Expected names and/or town_name references in the part.", self.pos) if len(self.pieces) > 255: raise generic.ScriptError("Too many values in a part, found %d, maximum is 255" % len(self.pieces), self.pos) return actFs, self def move_pieces(self): """ Move pieces to new action F instances to make it fit, if needed. @return: Created action F instances. @rtype: C{list} of L{ActionF} @note: Function may change L{pieces}. """ global townname_serial if len(self.pieces) <= 255: return [] # Trivially correct. # There are too many pieces. nactf = (len(self.pieces) + 254) // 255 pow2 = 1 while pow2 < nactf: pow2 = pow2 * 2 if pow2 < 255: nactf = pow2 heap = [] # Heap of (summed probability, subset-of-pieces) i = 0 while i < nactf: # Index 'i' is added to have a unique sorting when lists have equal total probabilities. heapq.heappush(heap, (0, i, [])) i = i + 1 # Index 'idx' is added to have a unique sorting when pieces have equal probabilities. rev_pieces = sorted(((p.probability.value, idx, p) for idx, p in enumerate(self.pieces)), reverse = True) for prob, _idx, piece in rev_pieces: sub = heapq.heappop(heap) sub[2].append(piece) sub = (sub[0] + prob, sub[1], sub[2]) heapq.heappush(heap, sub) # To ensure the chances do not get messed up due to one part needing less bits for its # selection, all parts are forced to use the same number of bits. max_prob = max(sub[0] for sub in heap) num_bits = 1 while (1 << num_bits) < max_prob: num_bits = num_bits + 1 # Assign to action F actFs = [] for _prob, _idx, sub in heap: actF_name = expression.Identifier("**townname #%d**" % townname_serial, None) townname_serial = townname_serial + 1 town_part = TownNamesPart(sub, self.pos) town_part.set_numbits(num_bits) actF = actionF.ActionF(actF_name, None, None, [town_part], self.pos) actFs.append(actF) # Remove pieces of 'sub' from self.pieces counts = len(self.pieces), len(sub) sub_set = set(sub) self.pieces = [piece for piece in self.pieces if piece not in sub_set] assert len(self.pieces) == counts[0] - counts[1] self.pieces.append(TownNamesEntryDefinition(actF_name, expression.ConstantNumeric(1), self.pos)) # update self.parts return actFs def assign_bits(self, startbit): """ Assign bits for this piece. @param startbit: First bit free for use. @type startbit: C{int} @return: Number of bits needed for this piece. @rtype: C{int} """ assert len(self.pieces) <= 255 total = sum(piece.probability.value for piece in self.pieces) self.startbit = startbit if self.num_bits is None: n = 1 while total > (1 << n): n = n + 1 self.num_bits = n assert (1 << self.num_bits) >= total return self.num_bits def set_numbits(self, numbits): """ Set the number of bits that this part should use. """ assert self.num_bits is None self.num_bits = numbits def debug_print(self, indentation): total = sum(piece.probability.value for piece in self.pieces) print indentation*' ' + 'Town names part (total %d)' % total for piece in self.pieces: piece.debug_print(indentation + 2, total) def __str__(self): return '{\n\t%s\n}\n' % '\n\t'.join([str(piece) for piece in self.pieces]) def get_length(self): size = 3 # textcount, firstbit, bitcount bytes. size += sum(piece.get_length() for piece in self.pieces) return size def resolve_townname_id(self): ''' Resolve the reference numbers to previous C{town_names} blocks. @return: Set of referenced C{town_names} block numbers. ''' blocks = set() for piece in self.pieces: block = piece.resolve_townname_id() if block is not None: blocks.add(block) return blocks def write(self, file): file.print_bytex(len(self.pieces)) file.print_bytex(self.startbit) file.print_bytex(self.num_bits) for piece in self.pieces: piece.write(file) file.newline() class TownNamesParam(object): """ Class containing a parameter of a town name. Currently known key/values: - 'styles' / string expression """ def __init__(self, key, value, pos): self.key = key self.value = value self.pos = pos def __str__(self): return '%s: %s;\n' % (self.key, self.value) class TownNamesEntryDefinition(object): """ An entry in a part referring to a non-final town name, with a given probability. @ivar def_number: Name or number referring to a previous town_names node. @type def_number: L{Identifier} or L{ConstantNumeric} @ivar number: Actual ID to use. @type number: C{None} or C{int} @ivar probability: Probability of picking this reference. @type probability: C{ConstantNumeric} @ivar pos: Position information of the parts block. @type pos: L{Position} """ def __init__(self, def_number, probability, pos): self.def_number = def_number self.number = None self.probability = probability self.pos = pos def pre_process(self): self.number = None if not isinstance(self.def_number, expression.Identifier): self.def_number = self.def_number.reduce_constant() if not isinstance(self.def_number, expression.ConstantNumeric): raise generic.ScriptError("Reference to other town name ID should be an integer number.", self.pos) if self.def_number.value < 0 or self.def_number.value > 0x7f: raise generic.ScriptError("Reference number out of range (must be between 0 and 0x7f inclusive).", self.pos) self.probability = self.probability.reduce_constant() if not isinstance(self.probability, expression.ConstantNumeric): raise generic.ScriptError("Probability should be an integer number.", self.pos) if self.probability.value < 0 or self.probability.value > 0x7f: raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): if isinstance(self.def_number, expression.Identifier): name_text = "name '" + self.def_number.value + "'" else: name_text = "number 0x%x" % self.def_number.value print indentation*' ' + ('Insert town_name ID %s with probability %d/%d' % (name_text, self.probability.value, total)) def __str__(self): return 'town_names(%s, %d),' % (str(self.def_number), self.probability.value) def get_length(self): return 2 def resolve_townname_id(self): ''' Resolve the reference number to a previous C{town_names} block. @return: Number of the referenced C{town_names} block. ''' if isinstance(self.def_number, expression.Identifier): self.number = actionF.named_numbers.get(self.def_number.value) if self.number is None: raise generic.ScriptError('Town names name "%s" is not defined or points to a next town_names node' % self.def_number.value, self.pos) else: self.number = self.def_number.value if self.number not in actionF.numbered_numbers: raise generic.ScriptError('Town names number "%s" is not defined or points to a next town_names node' % self.number, self.pos) return self.number def write(self, file): file.print_bytex(self.probability.value | 0x80) file.print_bytex(self.number) class TownNamesEntryText(object): """ An entry in a part, a text-string with a given probability. @ivar pos: Position information of the parts block. @type pos: L{Position} """ def __init__(self, id, text, probability, pos): self.id = id self.text = text self.probability = probability self.pos = pos def pre_process(self): if self.id.value != 'text': raise generic.ScriptError("Expected 'text' prefix.", self.pos) if not isinstance(self.text, expression.StringLiteral): raise generic.ScriptError("Expected string literal for the name.", self.pos) self.probability = self.probability.reduce_constant() if not isinstance(self.probability, expression.ConstantNumeric): raise generic.ScriptError("Probability should be an integer number.", self.pos) if self.probability.value < 0 or self.probability.value > 0x7f: raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): print indentation*' ' + ('Text %s with probability %d/%d' % (self.text.value, self.probability.value, total)) def __str__(self): return 'text(%s, %d),' % (str(self.text), self.probability.value) def get_length(self): return 1 + grfstrings.get_string_size(self.text.value) # probability, text def resolve_townname_id(self): ''' Resolve the reference number to a previous C{town_names} block. @return: C{None}, as being the block number of a referenced previous C{town_names} block. ''' return None def write(self, file): file.print_bytex(self.probability.value) file.print_string(self.text.value, final_zero = True) nml-0.2.4/nml/ast/alt_sprites.py0000644000061700006170000001537412036626442017326 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import real_sprite from nml.ast import base_statement import os, Image """ List with all AltSpritesBlocks encountered in the nml file. """ alt_sprites_list = [] class AltSpritesBlock(base_statement.BaseStatement): """ AST Node for alternative graphics. These are normally 32bpp graphics, possible for a higher zoom-level than the default sprites. @ivar name: The name of the replace/font_glyph/replace_new/spriteblock-block this block contains alternative graphics for. @type name: L{expression.Identifier} @ivar zoom_level: The zoomlevel these graphics are for. @type zoom_level: L{expression.Expression} @ivar pcx: Default graphics file for the sprites in this block. @type pcx: L{expression.StringLiteral} or C{None} @ivar sprite_list: List of real sprites or templates expanding to real sprites. @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "alt_sprites-block", pos) if not (2 <= len(param_list) <= 3): raise generic.ScriptError("alternative_sprites-block requires 2 or 3 parameters, encountered " + str(len(param_list)), pos) self.name = param_list[0] self.zoom_level = param_list[1] self.pcx = param_list[2] if len(param_list) >= 3 else None self.sprite_list = sprite_list def pre_process(self): if self.pcx: self.pcx.reduce() if not isinstance(self.pcx, expression.StringLiteral): raise generic.ScriptError("alternative_sprites-block parameter 3 'file' must be a string literal", self.pcx.pos) alt_sprites_list.append(self) def debug_print(self, indentation): print indentation*' ' + 'Alternative sprites' print (indentation+2)*' ' + 'Replacement for sprite:', str(self.name) print (indentation+2)*' ' + 'Zoom level:', str(self.zoom_level) print (indentation+2)*' ' + 'Source:', self.pcx.value if self.pcx is not None else 'None' print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): # Alternative sprites are not part of the final grf/nfo file, they're wirtten # as separate png files intsead. As such we don't return any actions. Creating # the png files happens in process. return [] def process(self, dir_name, block_names): """ Create seperate png files for every sprite. For OpenTTD to be able to read those files they have to be in a directory with the same name as the grf and have .png as name. For the extra-zoom-levels branch the png files have to be named _z.png. The default zoom level is number 2. @param dir_name: The name of the directory where to create the png files. @type dir_name: C{str} @param block_names: Mapping of block-names to sprite ids. @type block_names: C{dict} of C{str} to C{int} """ if not os.path.exists(dir_name): os.makedirs(dir_name) sprite_id = self.name.reduce_constant([block_names]).value zoom_level = self.zoom_level.reduce_constant(global_constants.const_list).value sprite_list = [action.sprite for action in real_sprite.parse_sprite_list(self.sprite_list, self.pcx)] for sprite in sprite_list: if sprite.is_empty: sprite_id += 1 continue # Both the extra-zoom-levels branch and clean trunk support the filename # without _z2 at the end. Higher zoom-levels are not supported by clean # trunk anyway, so follow the format needed for extra-zoom-levels there. postfix = "" if zoom_level == 2 else "_z" + str(zoom_level) filename = os.path.join(dir_name, str(sprite_id) + postfix + ".png") mask_filename = os.path.join(dir_name, str(sprite_id) + postfix + "m.png") write_32bpp_sprite(sprite, filename, mask_filename) sprite_id += 1 def write_32bpp_sprite(sprite_info, filename, mask_filename): """ Actually write a png file with a single sprite. @param sprite_info: Information about filename and offsets/size of the sprite in the file. @type sprite_info: L{RealSprite} @param filename: Name of the file to create. @type filename: C{str} """ sprite_info.validate_size() if not os.path.exists(sprite_info.file.value): raise generic.ImageError("File doesn't exist", sprite_info.file.value) im = Image.open(sprite_info.file.value) if im.mode != "RGBA": raise generic.ImageError("Not an RGBA image", sprite_info.file.value) x = sprite_info.xpos.value y = sprite_info.ypos.value size_x = sprite_info.xsize.value size_y = sprite_info.ysize.value sprite = im.crop((x, y, x + size_x, y + size_y)) sprite.info["x_offs"] = str(sprite_info.xrel.value) sprite.info["y_offs"] = str(sprite_info.yrel.value) pngsave(sprite, filename) if sprite_info.mask_file: im = Image.open(sprite_info.mask_file.value) if im.mode != "P": raise generic.ImageError("Not a paletted image", sprite_info.file.value) sprite = im.crop((x, y, x + size_x, y + size_y)) pngsave(sprite, mask_filename) def pngsave(im, file): """ Wrapper around PIL 1.1.6 Image.save to preserve PNG metadata. public domain, Nick Galbreath http://blog.modp.com/2007/08/python-pil-and-png-metadata-take-2.html """ # these can be automatically added to Image.info dict # they are not user-added metadata reserved = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect') # undocumented class from PIL import PngImagePlugin meta = PngImagePlugin.PngInfo() # copy metadata into new object for k, v in im.info.iteritems(): if k in reserved: continue meta.add_text(k, v, 0) # and save im.save(file, "PNG", pnginfo=meta) nml-0.2.4/nml/ast/spriteblock.py0000644000061700006170000002462412036626442017314 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action1, action2, action2layout, action2real, real_sprite from nml.ast import base_statement class TemplateDeclaration(base_statement.BaseStatement): def __init__(self, name, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "template declaration", pos, False, False) self.name = name self.param_list = param_list self.sprite_list = sprite_list def pre_process(self): #check that all templates that are referred to exist at this point #This prevents circular dependencies for sprite in self.sprite_list: if isinstance(sprite, real_sprite.TemplateUsage): if sprite.name.value == self.name.value: raise generic.ScriptError("Sprite template '%s' includes itself." % sprite.name.value, self.pos) elif sprite.name.value not in real_sprite.sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + sprite.name.value, sprite.pos) #Register template if self.name.value not in real_sprite.sprite_template_map: real_sprite.sprite_template_map[self.name.value] = self else: raise generic.ScriptError("Template named '%s' is already defined, first definition at %s" % (self.name.value, real_sprite.sprite_template_map[self.name.value].pos), self.pos) def get_labels(self): labels = {} offset = 0 for sprite in self.sprite_list: sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in sprite_labels.iteritems(): if lbl in labels: raise generic.ScriptError("Duplicate label encountered; '%s' already exists." % lbl, self.pos) labels[lbl] = lbl_offset + offset offset += num_sprites return labels, offset def debug_print(self, indentation): print indentation*' ' + 'Template declaration:', self.name.value print (indentation+2)*' ' + 'Parameters:' for param in self.param_list: param.debug_print(indentation + 4) print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): ret = "template %s(%s) {\n" % (str(self.name), ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: ret += "\t%s\n" % str(sprite) ret += "}\n" return ret spriteset_base_class = action2.make_sprite_group_class(True, True, False, cls_is_relocatable = True) class SpriteSet(spriteset_base_class): def __init__(self, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "spriteset", pos, False, False) if not (1 <= len(param_list) <= 2): raise generic.ScriptError("Spriteset requires 1 or 2 parameters, encountered " + str(len(param_list)), pos) name = param_list[0] if not isinstance(name, expression.Identifier): raise generic.ScriptError("Spriteset parameter 1 'name' should be an identifier", name.pos) self.initialize(name) if len(param_list) >= 2: self.pcx = param_list[1].reduce() if not isinstance(self.pcx, expression.StringLiteral): raise generic.ScriptError("Spriteset-block parameter 2 'file' must be a string literal", self.pcx.pos) else: self.pcx = None self.sprite_list = sprite_list self.action1_num = None #set number in action1 self.labels = {} #mapping of real sprite labels to offsets def pre_process(self): spriteset_base_class.pre_process(self) offset = 0 for sprite in self.sprite_list: sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in sprite_labels.iteritems(): if lbl in self.labels: raise generic.ScriptError("Duplicate label encountered; '%s' already exists." % lbl, self.pos) self.labels[lbl] = lbl_offset + offset offset += num_sprites def collect_references(self): return [] def debug_print(self, indentation): print indentation*' ' + 'Sprite set:', self.name.value print (indentation+2)*' ' + 'Source: ', self.pcx.value if self.pcx is not None else 'None' print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): # Actions are created when parsing the action2s, not here return [] def __str__(self): filename = (", " + str(self.pcx)) if self.pcx is not None else "" ret = "spriteset(%s%s) {\n" % (self.name, filename) for sprite in self.sprite_list: ret += "\t%s\n" % str(sprite) ret += "}\n" return ret spritegroup_base_class = action2.make_sprite_group_class(False, True, False) class SpriteGroup(spritegroup_base_class): def __init__(self, name, spriteview_list, pos = None): base_statement.BaseStatement.__init__(self, "spritegroup", pos, False, False) self.initialize(name) self.spriteview_list = spriteview_list def pre_process(self): for spriteview in self.spriteview_list: spriteview.pre_process() spritegroup_base_class.pre_process(self) def collect_references(self): return [] def debug_print(self, indentation): print indentation*' ' + 'Sprite group:', self.name.value for spriteview in self.spriteview_list: spriteview.debug_print(indentation + 2) def get_action_list(self): action_list = [] if self.prepare_output(): for feature in sorted(self.feature_set): action_list.extend(action2real.get_real_action2s(self, feature)) return action_list def __str__(self): ret = "spritegroup %s {\n" % (self.name) for spriteview in self.spriteview_list: ret += "\t%s\n" % str(spriteview) ret += "}\n" return ret class SpriteView(object): def __init__(self, name, spriteset_list, pos): self.name = name self.spriteset_list = spriteset_list self.pos = pos def pre_process(self): self.spriteset_list = [x.reduce(global_constants.const_list) for x in self.spriteset_list] for sg_ref in self.spriteset_list: if not (isinstance(sg_ref, expression.SpriteGroupRef) and action2.resolve_spritegroup(sg_ref.name).is_spriteset()): raise generic.ScriptError("Expected a sprite set reference", sg_ref.pos) if len(sg_ref.param_list) != 0: raise generic.ScriptError("Spritesets referenced from a spritegroup may not have parameters.", sg_ref.pos) def debug_print(self, indentation): print indentation*' ' + 'Sprite view:', self.name.value print (indentation+2)*' ' + 'Sprite sets:' for spriteset in self.spriteset_list: spriteset.debug_print(indentation + 4) def __str__(self): return "%s: [%s];" % (str(self.name), ", ".join([str(spriteset) for spriteset in self.spriteset_list])) spritelayout_base_class = action2.make_sprite_group_class(False, True, False) class SpriteLayout(spritelayout_base_class): def __init__(self, name, param_list, layout_sprite_list, pos = None): base_statement.BaseStatement.__init__(self, "spritelayout", pos, False, False) self.initialize(name, None, len(param_list)) self.param_list = param_list self.register_map = {} # Set during action generation for easier referencing self.layout_sprite_list = layout_sprite_list # Do not reduce expressions here as they may contain variables # And the feature is not known yet def pre_process(self): # Check parameter names seen_names = set() for param in self.param_list: if not isinstance(param, expression.Identifier): raise generic.ScriptError("spritelayout parameter names must be identifiers.", param.pos) if param.value in seen_names: raise generic.ScriptError("Duplicate parameter name '%s' encountered." % param.value, param.pos) seen_names.add(param.value) spritelayout_base_class.pre_process(self) def collect_references(self): return [] def debug_print(self, indentation): print indentation*' ' + 'Sprite layout:', self.name.value print (indentation+2)*' ' + 'Parameters:' for param in self.param_list: param.debug_print(indentation + 4) print (indentation+2)*' ' + 'Sprites:' for layout_sprite in self.layout_sprite_list: layout_sprite.debug_print(indentation + 4) def __str__(self): return 'spritelayout %s {\n%s\n}\n' % (str(self.name), '\n'.join([str(x) for x in self.layout_sprite_list])) def get_action_list(self): action_list = [] if self.prepare_output(): for feature in sorted(self.feature_set): action_list.extend(action2layout.get_layout_action2s(self, feature)) return action_list class LayoutSprite(object): def __init__(self, ls_type, param_list, pos): self.type = ls_type self.param_list = param_list self.pos = pos def debug_print(self, indentation): print indentation*' ' + 'Tile layout sprite of type:', self.type for layout_param in self.param_list: layout_param.debug_print(indentation + 2) def __str__(self): return '\t%s {\n\t\t%s\n\t}' % (self.type, '\n\t\t'.join([str(layout_param) for layout_param in self.param_list])) nml-0.2.4/nml/ast/__init__.py0000644000061700006170000000124312036626441016521 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.2.4/nml/ast/conditional.py0000644000061700006170000000542412036626441017272 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import global_constants from nml.actions import action7 from nml.ast import base_statement class ConditionalList(base_statement.BaseStatementList): """ Wrapper for a complete if/else if/else if/else block. """ def __init__(self, conditionals): assert len(conditionals) > 0 base_statement.BaseStatementList.__init__(self, "if/else-block", conditionals[0].pos, base_statement.BaseStatementList.LIST_TYPE_SKIP, conditionals, in_item = True) def get_action_list(self): return action7.parse_conditional_block(self) def debug_print(self, indentation): print indentation*' ' + 'Conditional' base_statement.BaseStatementList.debug_print(self, indentation + 2) def __str__(self): ret = '' ret += ' else '.join([str(stmt) for stmt in self.statements]) ret += '\n' return ret class Conditional(base_statement.BaseStatementList): """ Condition along with the code that has to be executed if the condition evaluates to some value not equal to 0. @ivar expr: The expression where the execution of code in this block depends on. @type expr: L{Expression} """ def __init__(self, expr, block, pos): base_statement.BaseStatementList.__init__(self, "if/else-block", pos, base_statement.BaseStatementList.LIST_TYPE_SKIP, block, in_item = True) self.expr = expr def pre_process(self): if self.expr is not None: self.expr = self.expr.reduce(global_constants.const_list) base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): if self.expr is not None: print indentation*' ' + 'Expression:' self.expr.debug_print(indentation + 2) print indentation*' ' + 'Block:' base_statement.BaseStatementList.debug_print(self, indentation + 2) def __str__(self): ret = '' if self.expr is not None: ret += 'if (%s)' % str(self.expr) ret += ' {\n' ret += base_statement.BaseStatementList.__str__(self) ret += '}\n' return ret nml-0.2.4/nml/ast/replace.py0000644000061700006170000001433412036626442016403 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import actionA, action5 from nml.ast import base_statement class ReplaceSprite(base_statement.BaseStatement): """ AST node for a 'replace' block. NML syntax: replace(start_id[, default_file]) { ..real sprites.. } @ivar param_list: List of parameters passed to the replace-block @type param_list: C{list} of L{Expression} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} @ivar start_id: First sprite to replace. Extracted from C{param_list} during pre-processing. @type start_id: C{Expression} @ivar pcx: Default image file to use for sprites. Extracted from C{param_list} during pre-processing. @type pcx: C{None} if not specified, else L{StringLiteral} @ivar name: Name of this block. @type name: C{None] if not given, else C{str} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replace-block", pos) self.param_list = param_list self.sprite_list = sprite_list self.name = name def pre_process(self): num_params = len(self.param_list) if not (1 <= num_params <= 2): raise generic.ScriptError("replace-block requires 1 or 2 parameters, encountered " + str(num_params), self.pos) self.start_id = self.param_list[0].reduce(global_constants.const_list) if num_params >= 2: self.pcx = self.param_list[1].reduce() if not isinstance(self.pcx, expression.StringLiteral): raise generic.ScriptError("replace-block parameter 2 'file' must be a string literal", self.pcx.pos) else: self.pcx = None def debug_print(self, indentation): print indentation*' ' + 'Replace sprites starting at' self.start_id.debug_print(indentation+2) print (indentation+2)*' ' + 'Source:', self.pcx.value if self.pcx is not None else 'None' if self.name: print (indentation+2)*' ' + 'Name:', self.name print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return actionA.parse_actionA(self) def __str__(self): name = str(self.name) if self.name is not None else "" ret = "replace %s(%s) {\n" % (name, ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: ret += "\t%s\n" % str(sprite) ret += "}\n" return ret class ReplaceNewSprite(base_statement.BaseStatement): """ AST node for a 'replacenew' block. NML syntax: replacenew(type[, default_file[, offset]]) { ..real sprites.. } @ivar param_list: List of parameters passed to the replacenew-block @type param_list: C{list} of L{Expression} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} @ivar type: Type of sprites to replace. Extracted from C{param_list} during pre-processing. @type type: L{Identifier} @ivar pcx: Default image file to use for sprites. Extracted from C{param_list} during pre-processing. @type pcx: C{None} if not specified, else L{StringLiteral} @ivar offset: Offset into the block of sprites. Extracted from C{param_list} during pre-processing. @type offset: C{int} @ivar name: Name of this block. @type name: C{None] if not given, else C{str} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replacenew-block", pos) self.param_list = param_list self.sprite_list = sprite_list self.name = name def pre_process(self): num_params = len(self.param_list) if not (1 <= num_params <= 3): raise generic.ScriptError("replacenew-block requires 1 to 3 parameters, encountered " + str(num_params), self.pos) self.type = self.param_list[0] if not isinstance(self.type, expression.Identifier): raise generic.ScriptError("replacenew parameter 'type' must be an identifier of a sprite replacement type", self.type.pos) if num_params >= 2: self.pcx = self.param_list[1].reduce() if not isinstance(self.pcx, expression.StringLiteral): raise generic.ScriptError("replacenew-block parameter 2 'file' must be a string literal", self.pcx.pos) else: self.pcx = None if num_params >= 3: self.offset = self.param_list[2].reduce_constant().value generic.check_range(self.offset, 0, 0xFFFF, "replacenew-block parameter 3 'offset'", self.param_list[2].pos) else: self.offset = 0 def debug_print(self, indentation): print indentation*' ' + 'Replace sprites for new features of type', self.type print (indentation+2)*' ' + 'Offset: ', self.offset print (indentation+2)*' ' + 'Source: ', self.pcx.value if self.pcx is not None else 'None' print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return action5.parse_action5(self) def __str__(self): name = str(self.name) if self.name is not None else "" ret = "replacenew %s(%s) {\n" % (name, ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: ret += "\t%s\n" % str(sprite) ret += "}\n" return ret nml-0.2.4/nml/ast/base_sprites.py0000644000061700006170000000634412036626442017455 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import real_sprite from nml.ast import base_statement class BaseSprite(base_statement.BaseStatement): """ AST node for a 'base_sprite' block. NML syntax: base_sprite [block_name]([default_file]) { ..real sprites.. } @ivar param_list: List of parameters passed to the replace-block @type param_list: C{list} of L{Expression} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} @ivar pcx: Default image file to use for sprites. Extracted from C{param_list} during pre-processing. @type pcx: C{None} if not specified, else L{StringLiteral} @ivar name: Name of this block. @type name: C{None] if not given, else C{str} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "base_sprites-block", pos) self.param_list = param_list self.sprite_list = sprite_list self.sprite_num = None self.name = name def pre_process(self): num_params = len(self.param_list) if not (0 <= num_params <= 2): raise generic.ScriptError("base_sprites-block requires 0 to 2 parameters, encountered %d" % num_params, self.pos) if num_params >= 2: self.sprite_num = self.param_list[0].reduce_constant() if num_params >= 1: self.pcx = self.param_list[-1].reduce() if not isinstance(self.pcx, expression.StringLiteral): raise generic.ScriptError("The last base_sprites-block parameter 'file' must be a string literal", self.pcx.pos) else: self.pcx = None def debug_print(self, indentation): print indentation*' ' + 'Base_sprite-block' print (indentation+2)*' ' + 'Source:', self.pcx.value if self.pcx is not None else 'None' if self.name: print (indentation+2)*' ' + 'Name:', self.name print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): actions = real_sprite.parse_sprite_list(self.sprite_list, self.pcx, block_name = self.name) actions[0].sprite_num = self.sprite_num return actions def __str__(self): name = str(self.name) if self.name is not None else "" ret = "base_sprites %s(%s) {\n" % (name, ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: ret += "\t%s\n" % str(sprite) ret += "}\n" return ret nml-0.2.4/nml/ast/font.py0000644000061700006170000000411712036626442015734 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, expression from nml.actions import action12 from nml.ast import base_statement class FontGlyphBlock(base_statement.BaseStatement): def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "font_glpyh-block", pos) if not (2 <= len(param_list) <= 3): raise generic.ScriptError("font_glpyh-block requires 2 or 3 parameters, encountered " + str(len(param_list)), pos) self.font_size = param_list[0] self.base_char = param_list[1] self.pcx = param_list[2] if len(param_list) >= 3 else None self.sprite_list = sprite_list self.name = name def pre_process(self): if self.pcx: self.pcx.reduce() if not isinstance(self.pcx, expression.StringLiteral): raise generic.ScriptError("font_glpyh-block parameter 3 'file' must be a string literal", self.pcx.pos) def debug_print(self, indentation): print indentation*' ' + 'Load font glyphs, starting at', self.base_char print (indentation+2)*' ' + 'Font size: ', self.font_size print (indentation+2)*' ' + 'Source: ', self.pcx.value if self.pcx is not None else 'None' print (indentation+2)*' ' + 'Sprites:' for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return action12.parse_action12(self) nml-0.2.4/nml/ast/tilelayout.py0000644000061700006170000001253212036626441017160 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, global_constants from nml.actions import action0properties from nml.ast import base_statement, assignment class TileLayout(base_statement.BaseStatement): """ 'tile_layout' AST node. A TileLayout is a list of x,y-offset/tileID pairs. The x and y offsets are from the northernmost tile of the industry/airport. Additionally some extra properties can be stored in the TileLayout, like the orientation of the airport. @ivar name: The name of this layout by which it can be referenced later. @type name: C{str} @ivar tile_prop_list: List of offset/tileID and properties. @type tile_prop_list: C{list} of L{LayoutTile} and L{Assignment} @ivar tile_list: List of tile-offsets/tileIDs. @type tile_list: C{list} of C{LayoutTile} with constant x and y values. @ivar properties: table of all properties. Unknown property names are accepted and ignored. @type properties: C{dict} with C{str} keys and L{ConstantNumeric} values """ def __init__(self, name, tile_list, pos): base_statement.BaseStatement.__init__(self, "tile layout", pos, False, False) self.name = name.value self.tile_prop_list = tile_list self.tile_list = [] self.properties = {} def pre_process(self): for tileprop in self.tile_prop_list: if isinstance(tileprop, assignment.Assignment): name = tileprop.name.value if name in self.properties: raise generic.ScriptError("Duplicate property %s in tile layout" % name, tileprop.name.pos) self.properties[name] = tileprop.value.reduce_constant(global_constants.const_list) else: assert isinstance(tileprop, LayoutTile) x = tileprop.x.reduce_constant() y = tileprop.y.reduce_constant() tile = tileprop.tiletype.reduce(unknown_id_fatal = False) if isinstance(tile, expression.Identifier) and tile.value == 'clear': tile = expression.ConstantNumeric(0xFF) self.tile_list.append(LayoutTile(x, y, tile)) if self.name in action0properties.tilelayout_names: raise generic.ScriptError("A tile layout with name '%s' has already been defined." % self.name, self.pos) action0properties.tilelayout_names[self.name] = self def debug_print(self, indentation): print indentation*' ' + 'TileLayout' for tile in self.tile_list: print (indentation+2)*' ' + 'At %d,%d:' % (tile.x, tile.y) tile.tiletype.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): return 'tilelayout %s {\n\t%s\n}\n' % (self.name, '\n\t'.join(str(x) for x in self.tile_prop_list)) def get_size(self): size = 2 for tile in self.tile_list: size += 3 if not isinstance(tile.tiletype, expression.ConstantNumeric): size += 2 return size def write(self, file): for tile in self.tile_list: file.print_bytex(tile.x.value) file.print_bytex(tile.y.value) if isinstance(tile.tiletype, expression.ConstantNumeric): file.print_bytex(tile.tiletype.value) else: if not isinstance(tile.tiletype, expression.Identifier): raise generic.ScriptError("Invalid expression type for layout tile", tile.tiletype.pos) if tile.tiletype.value not in global_constants.item_names: raise generic.ScriptError("Unknown tile name", tile.tiletype.pos) file.print_bytex(0xFE) tile_id = global_constants.item_names[tile.tiletype.value].id if not isinstance(tile_id, expression.ConstantNumeric): raise generic.ScriptError("Tile '%s' cannot be used in a tilelayout, as its ID is not a constant." % tile.tiletype.value, tile.tiletype.pos) file.print_wordx(tile_id.value) file.newline() file.print_bytex(0) file.print_bytex(0x80) file.newline() class LayoutTile(object): """ Single tile that is part of a L{TileLayout}. @ivar x: X-offset from the northernmost tile of the industry/airport. @type x: L{Expression} @ivar y: Y-offset from the northernmost tile of the industry/airport. @type y: L{Expression} @ivar tiletype: TileID of the tile to draw on the given offset. @type tiletype: L{Expression} """ def __init__(self, x, y, tiletype): self.x = x self.y = y self.tiletype = tiletype def __str__(self): return '%s, %s: %s;' % (self.x, self.y, self.tiletype) nml-0.2.4/nml/ast/grf.py0000644000061700006170000002654212036626441015551 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings, global_constants from nml.actions import action8, action14 from nml.ast import base_statement palette_node = None blitter_node = None def set_palette_used(pal): """ Set the used palette in the action 14 node, if applicable @param pal: Palette to use @type pal: C{str} of length 1 """ if palette_node: palette_node.pal = pal def set_preferred_blitter(blitter): """ Set the preferred blitter in the action14 node, if applicable @param blitter: The blitter to set @type blitter: C{str} of length 1 """ if blitter_node: blitter_node.blitter = blitter class GRF(base_statement.BaseStatement): """ AST Node for a grf block, that supplies (static) information about the GRF This is equivalent to actions 8 and 14 @ivar name: Name of the GRF (short) @type name: L{Expression}, should be L{String} else user error @ivar desc: Description of the GRF (longer) @type name: L{Expression}, should be L{String} else user error @ivar grfid: Globally unique identifier of the GRF @type grfid: L{Expression}, should be L{StringLiteral} of 4 bytes else user error @ivar version: Version of this GRF @type version: L{Expression} @ivar min_compatible_version: Minimum (older) version of the same GRF that it is compatible with @type min_compatible_version: L{Expression} @ivar params: List of user-configurable GRF parameters @type params: C{list} of L{ParameterDescription} """ def __init__(self, alist, pos): base_statement.BaseStatement.__init__(self, "grf-block", pos, False, False) self.name = None self.desc = None self.url = None self.grfid = None self.version = None self.min_compatible_version = None self.params = [] generic.OnlyOnce.enforce(self, "GRF-block") for assignment in alist: if isinstance(assignment, ParameterDescription): self.params.append(assignment) elif assignment.name.value == "name": self.name = assignment.value elif assignment.name.value == "desc": self.desc = assignment.value elif assignment.name.value == "url": self.url = assignment.value elif assignment.name.value == "grfid": self.grfid = assignment.value elif assignment.name.value == "version": self.version = assignment.value elif assignment.name.value == "min_compatible_version": self.min_compatible_version = assignment.value else: raise generic.ScriptError("Unknown item in GRF-block: " + str(assignment.name), assignment.name.pos) def pre_process(self): if None in (self.name, self.desc, self.grfid, self.version, self.min_compatible_version): raise generic.ScriptError("A GRF-block requires the 'name', 'desc', 'grfid', 'version' and 'min_compatible_version' properties to be set.", self.pos) self.grfid = self.grfid.reduce() global_constants.constant_numbers['GRFID'] = expression.parse_string_to_dword(self.grfid) self.name = self.name.reduce() if not isinstance(self.name, expression.String): raise generic.ScriptError("GRF-name must be a string", self.name.pos) grfstrings.validate_string(self.name) self.desc = self.desc.reduce() if not isinstance(self.desc, expression.String): raise generic.ScriptError("GRF-description must be a string", self.desc.pos) grfstrings.validate_string(self.desc) if self.url is not None: self.url = self.url.reduce() if not isinstance(self.url, expression.String): raise generic.ScriptError("URL must be a string", self.url.pos) grfstrings.validate_string(self.url) self.version = self.version.reduce_constant() self.min_compatible_version = self.min_compatible_version.reduce_constant() param_num = 0 for param in self.params: param.pre_process(expression.ConstantNumeric(param_num)) param_num = param.num.value + 1 def debug_print(self, indentation): print indentation*' ' + 'GRF' print (2+indentation)*' ' + 'grfid:' self.grfid.debug_print(indentation + 4) print (2+indentation)*' ' + 'Name:' self.name.debug_print(indentation + 4) print (2+indentation)*' ' + 'Description:' self.desc.debug_print(indentation + 4) if self.url is not None: print (2+indentation)*' ' + 'URL:' self.url.debug_print(indentation + 4) print (2+indentation)*' ' + 'Version:' self.version.debug_print(indentation + 4) print (2+indentation)*' ' + 'Minimal compatible version:' self.min_compatible_version.debug_print(indentation + 4) def get_action_list(self): global palette_node, blitter_node palette_node = action14.UsedPaletteNode("A") blitter_node = action14.BlitterNode("8") action14_root = action14.BranchNode("INFO") action14.grf_name_desc_actions(action14_root, self.name, self.desc, self.url, self.version, self.min_compatible_version) action14.param_desc_actions(action14_root, self.params) action14_root.subnodes.append(palette_node) action14_root.subnodes.append(blitter_node) return action14.get_actions(action14_root) + [action8.Action8(self.grfid, self.name, self.desc)] def __str__(self): ret = 'grf {\n' ret += '\tgrfid: %s;\n' % str(self.grfid) ret += '\tname: %s;\n' % str(self.name) ret += '\tdesc: %s;\n' % str(self.desc) if self.url is not None: ret += '\turl: %s;\n' % str(self.url) ret += '\tversion: %s;\n' % str(self.version) ret += '\tmin_compatible_version: %s;\n' % str(self.min_compatible_version) for param in self.params: ret += str(param) ret += '}\n' return ret class ParameterSetting(object): def __init__(self, name, value_list): self.name = name self.value_list = value_list self.type = 'int' self.name_string = None self.desc_string = None self.min_val = None self.max_val = None self.def_val = None self.bit_num = None self.val_names = [] self.properties_set = set() def pre_process(self): for set_val in self.value_list: self.set_property(set_val.name.value, set_val.value) def __str__(self): ret = "\t\t%s {\n" % str(self.name) for val in self.value_list: if val.name.value == 'names': ret += "\t\t\tnames: {\n" for name in val.value.values: ret += "\t\t\t\t%s: %s;\n" % (name.name, name.value) ret += "\t\t\t};\n" else: ret += "\t\t\t%s: %s;\n" % (val.name, val.value) ret += "\t\t}\n" return ret def set_property(self, name, value): """ Set a single parameter property @param name: Name of the property to be set @type name: C{basestring} @param value: Value of the property (note: may be an array) @type value: L{Expression} """ if name in self.properties_set: raise generic.ScriptError("You cannot set the same property twice in a parameter description block", value.pos) self.properties_set.add(name) if name == 'names': for name_value in value.values: num = name_value.name.reduce_constant().value desc = name_value.value if not isinstance(desc, expression.String): raise generic.ScriptError("setting name description must be a string", desc.pos) self.val_names.append((num, desc)) return value = value.reduce(unknown_id_fatal = False) if name == 'type': if not isinstance(value, expression.Identifier) or (value.value != 'int' and value.value != 'bool'): raise generic.ScriptError("setting-type must be either 'int' or 'bool'", value.pos) self.type = value.value elif name == 'name': if not isinstance(value, expression.String): raise generic.ScriptError("setting-name must be a string", value.pos) self.name_string = value elif name == 'desc': if not isinstance(value, expression.String): raise generic.ScriptError("setting-description must be a string", value.pos) self.desc_string = value elif name == 'bit': if self.type != 'bool': raise generic.ScriptError("setting-bit is only valid for 'bool' settings", value.pos) self.bit_num = value.reduce_constant() elif name == 'min_value': if self.type != 'int': raise generic.ScriptError("setting-min_value is only valid for 'int' settings", value.pos) self.min_val = value.reduce_constant() elif name == 'max_value': if self.type != 'int': raise generic.ScriptError("setting-max_value is only valid for 'int' settings", value.pos) self.max_val = value.reduce_constant() elif name == 'def_value': self.def_val = value.reduce_constant() if self.type == 'bool' and self.def_val.value != 0 and self.def_val.value != 1: raise generic.ScriptError("setting-def_value must be either 0 or 1 for 'bool' settings", value.pos) else: raise generic.ScriptError("Unknown setting-property " + name, value.pos) class ParameterDescription(object): def __init__(self, setting_list, num = None, pos = None): self.setting_list = setting_list self.num = num self.pos = pos def __str__(self): ret = "\tparam" if self.num: ret += " " + str(self.num) ret += " {\n" for setting in self.setting_list: ret += str(setting) ret += "\t}\n" return ret def pre_process(self, num): if self.num is None: self.num = num self.num = self.num.reduce_constant() for setting in self.setting_list: setting.pre_process() for setting in self.setting_list: if setting.type == 'int': if len(self.setting_list) > 1: raise generic.ScriptError("When packing multiple settings in one parameter only bool settings are allowed", self.pos) global_constants.settings[setting.name.value] = {'num': self.num.value, 'size': 4} else: bit = 0 if setting.bit_num is None else setting.bit_num.value global_constants.misc_grf_bits[setting.name.value] = {'param': self.num.value, 'bit': bit} nml-0.2.4/nml/ast/general.py0000644000061700006170000000506312036626441016403 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.ast import base_statement feature_ids = { 'FEAT_TRAINS': 0x00, 'FEAT_ROADVEHS': 0x01, 'FEAT_SHIPS': 0x02, 'FEAT_AIRCRAFT': 0x03, 'FEAT_STATIONS': 0x04, 'FEAT_CANALS': 0x05, 'FEAT_BRIDGES': 0x06, 'FEAT_HOUSES': 0x07, 'FEAT_GLOBALVARS': 0x08, 'FEAT_INDUSTRYTILES': 0x09, 'FEAT_INDUSTRIES': 0x0A, 'FEAT_CARGOS': 0x0B, 'FEAT_SOUNDEFFECTS': 0x0C, 'FEAT_AIRPORTS': 0x0D, 'FEAT_SIGNALS': 0x0E, 'FEAT_OBJECTS': 0x0F, 'FEAT_RAILTYPES': 0x10, 'FEAT_AIRPORTTILES': 0x11, } def feature_name(feature): """ Given a feature number, return the identifier as normally used for that feature in nml files. @param feature: The feature number to convert to a string. @type feature: C{int} @return: String with feature as used in nml files. @rtype: C{str} """ for name, num in feature_ids.iteritems(): if num == feature: return name assert False, "Invalid feature number" def parse_feature(expr): """ Parse an expression into a valid feature number. @param expr: Expression to parse @type expr: L{Expression} @return: A constant number representing the parsed feature @rtype: L{ConstantNumeric} """ expr = expr.reduce_constant([feature_ids]) if expr.value not in feature_ids.values(): raise generic.ScriptError("Invalid feature '%02X' encountered." % expr.value, expr.pos) return expr class MainScript(base_statement.BaseStatementList): def __init__(self, statements): assert len(statements) > 0 base_statement.BaseStatementList.__init__(self, "main script", statements[0].pos, base_statement.BaseStatementList.LIST_TYPE_NONE, statements, False, False, False, False) def __str__(self): res = "" for stmt in self.statements: res += str(stmt) + '\n' return res nml-0.2.4/nml/ast/override.py0000644000061700006170000000455712036626441016614 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, global_constants from nml.ast import base_statement from nml.actions import action0 class EngineOverride(base_statement.BaseStatement): """ AST Node for an engine override. @ivar grfid: GRFid of the grf to override the engines from. @type grfid: C{int{ @ivar source_grfid: GRFid of the grf that overrides the engines. @type source_grfid: C{int} """ def __init__(self, args, pos): base_statement.BaseStatement.__init__(self, "engine_override()", pos) self.args = args def pre_process(self): if len(self.args) not in (1, 2): raise generic.ScriptError("engine_override expects 1 or 2 parameters", self.pos) if len(self.args) == 1: try: self.source_grfid = expression.Identifier('GRFID').reduce(global_constants.const_list).value assert isinstance(self.source_grfid, int) except generic.ScriptError: raise generic.ScriptError("GRFID of this grf is required, but no grf-block is defined.", self.pos) else: self.source_grfid = expression.parse_string_to_dword(self.args[0].reduce(global_constants.const_list)) self.grfid = expression.parse_string_to_dword(self.args[-1].reduce(global_constants.const_list)) def debug_print(self, indentation): print indentation*' ' + 'Engine override' print (indentation+2)*' ' + 'Source:', str(self.source_grfid) print (indentation+2)*' ' + 'Target:', str(self.grfid) def get_action_list(self): return action0.get_engine_override_action(self) def __str__(self): return "engine_override(%s);\n" % ', '.join(str(x) for x in self.args) nml-0.2.4/nml/ast/assignment.py0000644000061700006170000000430512036626441017134 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class Assignment(object): """ Simple storage container for a name / value pair. This class does not enforce any type information. Create a subclass to do this, or hard-code it in the parser. @ivar name: Name of the parameter / property / whatever to be set. @type name: Anything @ivar value: Value to assign to @type value: Anything @ivar pos: Position information of the assignment @type pos: L{Position} """ def __init__(self, name, value, pos): self.name = name self.value = value self.pos = pos def debug_print(self, indentation): print indentation*' ' + 'Assignment' print (indentation+2)*' ' + 'Name:' self.name.debug_print(indentation + 4) print (indentation+2)*' ' + 'Value:' self.value.debug_print(indentation + 4) def __str__(self): return "%s: %s;" % (str(self.name), str(self.value)) class Range(object): """ Storage container for a range of values (inclusive). This Contains a minimum value and optionally also a maximum value. If the maximum values is omitted, the minimum is also used as maximum. @ivar min: The minimum value of this range. @type min: L{Expression} @ivar max: THe maximum value of this range. @type max: L{Expression} or C{None} """ def __init__(self, min, max): self.min = min self.max = max def __str__(self): if self.max is None: return str(self.min) return "%s .. %s" % (str(self.min), str(self.max)) nml-0.2.4/nml/ast/base_statement.py0000644000061700006170000001367312036626441017772 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class BaseStatement(object): """ Base class for a statement (AST node) in NML. Note: All instance variables (except 'pos') are prefixed with "bs_" to avoid naming conflicts @ivar bs_name: Name of the statement type @type bs_name: C{str} @ivar pos: Position information @type pos: L{Position} @ivar bs_skipable: Whether the statement may be skipped using an if-block (default: True) @type bs_skipable: C{bool} @ivar bs_loopable: Whether the statement may be executed multiple times using a while-block (default: True) @type bs_loopable: C{bool} @pre: bs_skipable or not bs_loopable @ivar bs_in_item: Whether the statement may be part of an item block (default: False_ @type bs_in_item: C{bool} @ivar bs_out_item: Whether the statement may appear outside of item blocks (default: True) @type bs_out_item: C{bool} """ def __init__(self, name, pos, skipable = True, loopable = True, in_item = False, out_item = True): assert skipable or not loopable self.bs_name = name self.pos = pos self.bs_skipable = skipable self.bs_loopable = loopable self.bs_in_item = in_item self.bs_out_item = out_item def validate(self, scope_list): """ Validate that this statement is in a valid location @param scope_list: List of nested blocks containing this statement @type scope_list: C{list} of L{BaseStatementList} """ seen_item = False for scope in scope_list: if scope.list_type == BaseStatementList.LIST_TYPE_SKIP: if not self.bs_skipable: raise generic.ScriptError("%s may not appear inside a conditional block." % self.bs_name, self.pos) if scope.list_type == BaseStatementList.LIST_TYPE_LOOP: if not self.bs_loopable: raise generic.ScriptError("%s may not appear inside a loop." % self.bs_name, self.pos) if scope.list_type == BaseStatementList.LIST_TYPE_ITEM: seen_item = True if not self.bs_in_item: raise generic.ScriptError("%s may not appear inside an item block." % self.bs_name, self.pos) if not (seen_item or self.bs_out_item): raise generic.ScriptError("%s must appear inside an item block." % self.bs_name, self.pos) def register_names(self): """ Called to register identifiers, that must be available before their definition. """ pass def pre_process(self): """ Called to do any pre-processing before the actual action generation. For example, to remove identifiesr """ pass def debug_print(self, indentation): """ Print all AST information to the standard output @param indentation: Print all lines with at least C{indentation} spaces @type indentation: C{int} """ raise NotImplementedError('debug_print must be implemented in BaseStatement-subclass %r' % type(self)) def get_action_list(self): """ Generate a list of NFO actions associated with this statement @return: A list of action @rtype: C{list} of L{BaseAction} """ raise NotImplementedError('get_action_list must be implemented in BaseStatement-subclass %r' % type(self)) def __str__(self): """ Generate a string representing this statement in valid NML-code. @return: An NML string representing this action @rtype: C{str} """ raise NotImplementedError('__str__ must be implemented in BaseStatement-subclass %r' % type(self)) class BaseStatementList(BaseStatement): """ Base class for anything that contains a list of statements @ivar list_type: Type of this list, used for validation logic @type list_type: C{int}, see constants below @pre list_type in (LIST_TYPE_NONE, LIST_TYPE_SKIP, LIST_TYPE_LOOP, LIST_TYPE_ITEM) @ivar statements: List of sub-statements in this block @type statements: C{list} of L{BaseStatement} """ LIST_TYPE_NONE = 0 LIST_TYPE_SKIP = 1 LIST_TYPE_LOOP = 2 LIST_TYPE_ITEM = 3 def __init__(self, name, pos, list_type, statements, skipable = True, loopable = True, in_item = False, out_item = True): BaseStatement.__init__(self, name, pos, skipable, loopable, in_item, out_item) assert list_type in (self.LIST_TYPE_NONE, self.LIST_TYPE_SKIP, self.LIST_TYPE_LOOP, self.LIST_TYPE_ITEM) self.list_type = list_type self.statements = statements def validate(self, scope_list): new_list = scope_list + [self] for stmt in self.statements: stmt.validate(new_list) def register_names(self): for stmt in self.statements: stmt.register_names() def pre_process(self): for stmt in self.statements: stmt.pre_process() def debug_print(self, indentation): for stmt in self.statements: stmt.debug_print(indentation) def get_action_list(self): action_list = [] for stmt in self.statements: action_list.extend(stmt.get_action_list()) return action_list def __str__(self): res = "" for stmt in self.statements: res += '\t' + str(stmt).replace('\n', '\n\t')[0:-1] return res nml-0.2.4/nml/ast/railtypetable.py0000644000061700006170000000521012036626441017621 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression from nml.ast import assignment from nml.actions import action0 from nml.ast import base_statement class RailtypeTable(base_statement.BaseStatement): def __init__(self, railtype_list, pos): base_statement.BaseStatement.__init__(self, "rail type table", pos, False, False) self.railtype_list = railtype_list generic.OnlyOnce.enforce(self, "rail type table") global_constants.railtype_table.clear() def register_names(self): for i, railtype in enumerate(self.railtype_list): if isinstance(railtype, assignment.Assignment): name = railtype.name val_list = [] for rt in railtype.value: if isinstance(rt, expression.Identifier): val_list.append(expression.StringLiteral(rt.value, rt.pos)) else: val_list.append(rt) expression.parse_string_to_dword(val_list[-1]) self.railtype_list[i] = val_list if len(val_list) > 1 else val_list[0] else: name = railtype if isinstance(railtype, expression.Identifier): self.railtype_list[i] = expression.StringLiteral(railtype.value, railtype.pos) expression.parse_string_to_dword(self.railtype_list[i]) global_constants.railtype_table[name.value] = i def pre_process(self): pass def debug_print(self, indentation): print indentation*' ' + 'Railtype table' for railtype in self.railtype_list: print (indentation+2)*' ' + 'Railtype: %s' % str(railtype.value) def get_action_list(self): return action0.get_railtypelist_action(self.railtype_list) def __str__(self): ret = 'railtypetable {\n' ret += ', '.join([expression.identifier_to_print(railtype.value) for railtype in self.railtype_list]) ret += '\n}\n' return ret nml-0.2.4/nml/ast/snowline.py0000644000061700006170000001217712036626441016630 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import datetime from nml import generic, expression, nmlop from nml.actions import action0 from nml.ast import base_statement class Snowline(base_statement.BaseStatement): """ Snowline curve throughout the year. @ivar type: Type of snowline. @type type: C{str} @ivar date_heights: Height of the snow line at given days in the year. @type date_heights: C{list} of L{Assignment} """ def __init__(self, line_type, height_data, pos): base_statement.BaseStatement.__init__(self, "snowline-block", pos) if line_type.value not in ('equal', 'linear'): raise generic.ScriptError('Unknown type of snow line (only "equal" and "linear" are supported)', line_type.pos) self.type = line_type.value self.date_heights = height_data def debug_print(self, indentation): print indentation*' ' + 'Snowline (type=%s)' % self.type for dh in self.date_heights: dh.debug_print(indentation + 2) def __str__(self): return 'snowline (%s) {\n\t%s\n}\n' % (str(self.type), '\n\t'.join([str(x) for x in self.date_heights])) def get_action_list(self): return action0.get_snowlinetable_action(compute_table(self)) def compute_table(snowline): """ Compute the table with snowline height for each day of the year. @param snowline: Snowline definition. @type snowline: L{Snowline} @return: Table of 12*32 entries with snowline heights. @rtype: C{str} """ day_table = [None]*365 # Height at each day, starting at day 0 for dh in snowline.date_heights: doy = dh.name.reduce() if not isinstance(doy, expression.ConstantNumeric): raise generic.ScriptError('Day of year is not a compile-time constant', doy.pos) height = dh.value.reduce() if snowline.type != 'equal' and not isinstance(height, expression.ConstantNumeric): raise generic.ScriptError('Height is not a compile-time constant', height.pos) if doy.value < 1 or doy.value > 365: raise generic.ScriptError('Day of the year must be between 1 and 365', doy.pos) if isinstance(height, expression.ConstantNumeric) and (height.value < 2 or height.value > 29): raise generic.ScriptError('Height must be between 2 and 29', height.pos) day_table[doy.value - 1] = height # Find first specified point. start = 0 while start < 365 and day_table[start] is None: start = start + 1 if start == 365: raise generic.ScriptError('No heights given for the snowline table', snowline.pos) first_point = start while True: # Find second point from start end = start + 1 if end == 365: end = 0 while end != first_point and day_table[end] is None: end = end + 1 if end == 365: end = 0 # Fill the days between start and end (exclusive both border values) startvalue = day_table[start] endvalue = day_table[end] unwrapped_end = end if end < start: unwrapped_end += 365 if snowline.type == 'equal': for day in range(start + 1, unwrapped_end): if day >= 365: day -= 365 day_table[day] = startvalue else: assert snowline.type == 'linear' if start != end: dhd = float(endvalue.value - startvalue.value) / float(unwrapped_end - start) else: assert startvalue.value == endvalue.value dhd = 0 for day in range(start + 1, unwrapped_end): uday = day if uday >= 365: uday -= 365 height = startvalue.value + int(round(dhd * (day - start))) day_table[uday] = expression.ConstantNumeric(height) if end == first_point: # All days done break start = end table = [None] * (12*32) for dy in range(365): today = datetime.date.fromordinal(dy + 1) if day_table[dy]: expr = expression.BinOp(nmlop.MUL, day_table[dy], expression.ConstantNumeric(8)).reduce() else: expr = None table[(today.month - 1) * 32 + today.day - 1] = expr for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] #Second loop is needed because we need make sure the first item is also set. for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] return table nml-0.2.4/nml/palette.py0000644000061700006170000004714112036626441015640 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic raw_palette_data = [ #DOS palette [ 0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 255, 255, 255 ], #end of DOS palette #Windows palette [ 0, 0, 255, 238, 0, 238, 239, 0, 239, 240, 0, 240, 241, 0, 241, 242, 0, 242, 243, 0, 243, 244, 0, 244, 245, 0, 245, 246, 0, 246, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 100, 100, 100, 116, 116, 116, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 132, 132, 132, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 64, 64, 64, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 80, 80, 80, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 48, 48, 48, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 16, 16, 16, 32, 32, 32, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 148, 148, 148, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 255, 255, 255 ], #end of Windows palette # DOS Toyland palette [ 0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 28, 108, 124, 32, 112, 128, 36, 116, 132, 40, 120, 136, 44, 124, 140, 92, 164, 184, 116, 180, 196, 216, 244, 252, 112, 176, 192, 88, 160, 180, 255, 255, 255 ], #end of DOS Toyland palette #Windows Toyland palette [ 0, 255, 255, 238, 0, 238, 239, 0, 239, 240, 0, 240, 241, 0, 241, 242, 0, 242, 243, 0, 243, 244, 0, 244, 245, 0, 245, 246, 0, 246, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 100, 100, 100, 116, 116, 116, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 132, 132, 132, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 64, 64, 64, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 80, 80, 80, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 48, 48, 48, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 16, 16, 16, 32, 32, 32, 28, 108, 124, 32, 112, 128, 36, 116, 132, 40, 120, 136, 44, 124, 140, 92, 164, 184, 116, 180, 196, 216, 244, 252, 112, 176, 192, 88, 160, 180, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 148, 148, 148, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 255, 255, 255 ] #end of Windows Toyland palette ] #Convert palettes to strings for fast comparision palette_data = [''.join([chr(c) for c in pal]) for pal in raw_palette_data] palette_name = ["DOS", "WIN", "DOS_TOYLAND", "WIN_TOYLAND"] def validate_palette(image, filename): palette = image.palette.palette if len(palette) != 768: raise generic.ImageError("Invalid palette; does not contain 256 entries.", filename) for i, pal in enumerate(palette_data): if pal != palette: continue return palette_name[i] raise generic.ImageError("Palette is not recognized as a valid palette.", filename) nml-0.2.4/nml/actions/0000755000061700006170000000000012036626604015262 5ustar abuildabuild00000000000000nml-0.2.4/nml/actions/action5.py0000644000061700006170000001242312036626442017200 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action, real_sprite class Action5(base_action.BaseAction): def __init__(self, type, num_sprites, offset): self.type = type self.num_sprites = num_sprites self.offset = offset def prepare_output(self): if self.offset is not None: self.type |= 0x80 def write(self, file): # * 05 [] size = 5 if self.offset is None else 8 file.start_sprite(size) file.print_bytex(0x05) file.print_bytex(self.type) file.print_bytex(0xFF) file.print_word(self.num_sprites) if self.offset is not None: file.print_bytex(0xFF) file.print_word(self.offset) file.newline() file.end_sprite() def skip_action7(self): #skipping with Action7 should work, according to the Action7/9 specs #However, skipping invalid (OpenTTD-only) Action5s in TTDP can only be done using Action9, else an error occurs #To be on the safe side, don't allow skipping with Action7 at all return False class Action5BlockType(object): FIXED = 0, #fixed number of sprites ANY = 1, #any number of sprites OFFSET = 2, #flexible number of sprites, offset may be set action5_table = { 'PRE_SIGNAL' : (0x04, 48, Action5BlockType.FIXED), 'PRE_SIGNAL_SEMAPHORE' : (0x04, 112, Action5BlockType.FIXED), 'PRE_SIGNAL_SEMAPHORE_PBS' : (0x04, 240, Action5BlockType.OFFSET), 'CATENARY' : (0x05, 48, Action5BlockType.OFFSET), 'FOUNDATIONS_SLOPES' : (0x06, 74, Action5BlockType.FIXED), 'FOUNDATIONS_SLOPES_HALFTILES' : (0x06, 90, Action5BlockType.OFFSET), 'TTDP_GUI_25' : (0x07, 73, Action5BlockType.FIXED), 'TTDP_GUI' : (0x07, 93, Action5BlockType.FIXED), 'CANALS' : (0x08, 65, Action5BlockType.OFFSET), 'ONE_WAY_ROAD' : (0x09, 6, Action5BlockType.OFFSET), 'COLOURMAP_2CC' : (0x0A, 256, Action5BlockType.OFFSET), 'TRAMWAY' : (0x0B, 113, Action5BlockType.OFFSET), 'SNOWY_TEMPERATE_TREES' : (0x0C, 133, Action5BlockType.FIXED), 'COAST_TILES' : (0x0D, 16, Action5BlockType.FIXED), 'COAST_TILES_BASEGFX' : (0x0D, 10, Action5BlockType.FIXED), 'COAST_TILES_DIAGONAL' : (0x0D, 18, Action5BlockType.FIXED), 'NEW_SIGNALS' : (0x0E, 0, Action5BlockType.ANY), 'SLOPED_RAILS' : (0x0F, 12, Action5BlockType.OFFSET), 'AIRPORTS' : (0x10, 15, Action5BlockType.OFFSET), 'ROAD_STOPS' : (0x11, 8, Action5BlockType.OFFSET), 'AQUEDUCTS' : (0x12, 8, Action5BlockType.OFFSET), 'AUTORAIL' : (0x13, 55, Action5BlockType.OFFSET), 'FLAGS' : (0x14, 36, Action5BlockType.OFFSET), 'OTTD_GUI' : (0x15, 166, Action5BlockType.OFFSET), 'AIRPORT_PREVIEW' : (0x16, 9, Action5BlockType.OFFSET), 'RAILTYPE_TUNNELS': (0x17, 16, Action5BlockType.OFFSET), } def parse_action5(replaces): real_sprite_list = real_sprite.parse_sprite_list(replaces.sprite_list, replaces.pcx, block_name = replaces.name) num_sprites = len(real_sprite_list) if replaces.type.value not in action5_table: raise generic.ScriptError(replaces.type.value + " is not a valid sprite replacement type", replaces.type.pos) type_id, num_required, block_type = action5_table[replaces.type.value] offset = None if block_type == Action5BlockType.FIXED: if num_sprites < num_required: raise generic.ScriptError("Invalid sprite count for sprite replacement type '%s', expcected %d, got %d" % (replaces.type, num_required, num_sprites), replaces.pos) elif num_sprites > num_required: generic.print_warning("Too many sprites specified for sprite replacement type '%s', expcected %d, got %d, extra sprites may be ignored" % (replaces.type, num_required, num_sprites), replaces.pos) if replaces.offset != 0: raise generic.ScriptError("replacenew parameter 'offset' must be zero for sprite replacement type '%s'" % replaces.type, replaces.pos) elif block_type == Action5BlockType.ANY: if replaces.offset != 0: raise generic.ScriptError("replacenew parameter 'offset' must be zero for sprite replacement type '%s'" % replaces.type, replaces.pos) elif block_type == Action5BlockType.OFFSET: if num_sprites + replaces.offset > num_required: generic.print_warning("Exceeding the limit of %d sprites for sprite replacement type '%s', extra sprites may be ignored" % (num_required, replaces.type), replaces.pos) if replaces.offset != 0 or num_sprites != num_required: offset = replaces.offset else: assert 0 return [Action5(type_id, num_sprites, offset)] + real_sprite_list nml-0.2.4/nml/actions/base_action.py0000644000061700006170000000345612036626442020113 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class BaseAction(object): def prepare_output(self): """ Called just before L{write}, this function can be used to do some last modifications to the action (like resolving some IDs that can't be resolved earlier). """ pass def write(self, file): """ Write this action to the given outputfile. @param file: The outputfile to write the data to. @type file: L{BinaryOutputBase} """ raise NotImplementedError('write is not implemented in %r' % type(self)) def skip_action7(self): """ Can this action be skipped safely by an action7? @return True iff this action can be skipped by action7. """ return True def skip_action9(self): """ Can this action be skipped safely by an action9? @return True iff this action can be skipped by action9. """ return True def skip_needed(self): """ Do we need to skip this action at all? @return False when it doesn't matter whether this action is skipped or not. """ return True nml-0.2.4/nml/actions/action3_callbacks.py0000644000061700006170000003457512036626442021211 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop callbacks = 0x12 * [{}] # Possible values for 'purchase': # 0 (or not set): not called from purchase list # 1: called normally and from purchase list # 2: only called from purchase list # 'cbname': as 1) but if 'cbname' is set also, then 'cbname' overrides this # in the purchase list. 'cbname' should have a value of 2 for 'purchase' # Callbacks common to all vehicle types general_vehicle_cbs = { 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, 'random_trigger' : {'type': 'cb', 'num': 0x01}, # Almost undocumented, but really neccesary! 'loading_speed' : {'type': 'cb', 'num': 0x12, 'flag_bit': 2}, 'cargo_subtype_text' : {'type': 'cb', 'num': 0x19, 'flag_bit': 5}, 'additional_text' : {'type': 'cb', 'num': 0x23, 'purchase': 2}, 'colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 'purchase_colour_mapping'}, 'purchase_colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 2}, 'start_stop' : {'type': 'cb', 'num': 0x31}, 'every_32_days' : {'type': 'cb', 'num': 0x32}, 'sound_effect' : {'type': 'cb', 'num': 0x33, 'flag_bit': 7}, 'refit_cost' : {'type': 'cb', 'num': 0x15E, 'purchase': 1}, } # Trains callbacks[0x00] = { 'visual_effect_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'shorten_vehicle' : {'type': 'cb', 'num': 0x11, 'flag_bit': 1}, # Should this become 'length' at some point (with inverted meaning)? 'cargo_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'can_attach_wagon' : {'type': 'cb', 'num': 0x1D}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'power' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 'purchase_power'}, 'purchase_power' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x16, 'purchase': 'purchase_weight'}, 'purchase_weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x16, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x17, 'purchase': 2}, 'tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 'purchase_tractive_effort_coefficient'}, 'purchase_tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 2}, 'bitmask_vehicle_info' : {'type': 'cb', 'num': 0x36, 'var10': 0x25}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x28}, } callbacks[0x00].update(general_vehicle_cbs) # Road vehicles callbacks[0x01] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'shorten_vehicle' : {'type': 'cb', 'num': 0x11, 'flag_bit': 1}, # Should this become 'length' at some point (with inverted meaning)? 'cargo_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, 'power' : {'type': 'cb', 'num': 0x36, 'var10': 0x13, 'purchase': 'purchase_power'}, 'purchase_power' : {'type': 'cb', 'num': 0x36, 'var10': 0x13, 'purchase': 2}, 'weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_weight'}, 'purchase_weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x15, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x15, 'purchase': 2}, 'tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x18, 'purchase': 'purchase_tractive_effort_coefficient'}, 'purchase_tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x18, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x22}, } callbacks[0x01].update(general_vehicle_cbs) # Ships callbacks[0x02] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'cargo_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0A, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x1D}, } callbacks[0x02].update(general_vehicle_cbs) # Aircraft callbacks[0x03] = { 'passenger_capacity' : [{'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_passenger_capacity'}], 'purchase_passenger_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0C, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0C, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0E, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0E, 'purchase': 2}, 'mail_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 'purchase_mail_capacity'}, 'purchase_mail_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x1C}, 'range' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 'purchase_range'}, 'purchase_range' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 2}, 'rotor' : {'type': 'override'}, } callbacks[0x03].update(general_vehicle_cbs) # Stations (0x04) are not yet implemented # Canals callbacks[0x05] = { 'sprite_offset' : {'type': 'cb', 'num': 0x147, 'flag_bit': 0}, 'default' : {'type': 'cargo', 'num': None}, } # Bridges (0x06) have no action3 # Houses callbacks[0x07] = { 'random_trigger' : {'type': 'cb', 'num': 0x01}, 'construction_check' : {'type': 'cb', 'num': 0x17, 'flag_bit': 0}, 'anim_next_frame' : {'type': 'cb', 'num': 0x1A, 'flag_bit': 1}, 'anim_control' : {'type': 'cb', 'num': 0x1B, 'flag_bit': 2}, 'construction_anim' : {'type': 'cb', 'num': 0x1C, 'flag_bit': 3}, 'colour' : {'type': 'cb', 'num': 0x1E, 'flag_bit': 4}, 'cargo_amount_accept' : {'type': 'cb', 'num': 0x1F, 'flag_bit': 5}, 'anim_speed' : {'type': 'cb', 'num': 0x20, 'flag_bit': 6}, 'destruction' : {'type': 'cb', 'num': 0x21, 'flag_bit': 7}, 'cargo_type_accept' : {'type': 'cb', 'num': 0x2A, 'flag_bit': 8}, 'cargo_production' : {'type': 'cb', 'num': 0x2E, 'flag_bit': 9}, 'protection' : {'type': 'cb', 'num': 0x143, 'flag_bit': 10}, 'watched_cargo_accepted' : {'type': 'cb', 'num': 0x148}, 'name' : {'type': 'cb', 'num': 0x14D}, 'foundations' : {'type': 'cb', 'num': 0x14E, 'flag_bit': 11}, 'autoslope' : {'type': 'cb', 'num': 0x14F, 'flag_bit': 12}, 'default' : {'type': 'cargo', 'num': None}, } # General variables (0x08) have no action3 # Industry tiles callbacks[0x09] = { 'random_trigger' : {'type': 'cb', 'num': 0x01}, 'anim_control' : {'type': 'cb', 'num': 0x25}, 'anim_next_frame' : {'type': 'cb', 'num': 0x26, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x27, 'flag_bit': 1}, 'cargo_amount_accept' : {'type': 'cb', 'num': 0x2B, 'flag_bit': 2}, # Should work like the industry CB, i.e. call multiple times 'cargo_type_accept' : {'type': 'cb', 'num': 0x2C, 'flag_bit': 3}, # Should work like the industry CB, i.e. call multiple times 'tile_check' : {'type': 'cb', 'num': 0x2F, 'flag_bit': 4}, 'foundations' : {'type': 'cb', 'num': 0x30, 'flag_bit': 5}, 'autoslope' : {'type': 'cb', 'num': 0x3B, 'flag_bit': 6}, 'default' : {'type': 'cargo', 'num': None}, } # Industries callbacks[0x0A] = { 'availability' : {'type': 'cb', 'num': 0x22, 'flag_bit': 0}, 'produce_cargo_arrival' : {'type': 'cb', 'num': 0x00, 'flag_bit': 1, 'var18': 0}, 'produce_256_ticks' : {'type': 'cb', 'num': 0x00, 'flag_bit': 2, 'var18': 1}, 'location_check' : {'type': 'cb', 'num': 0x28, 'flag_bit': 3}, # We need a way to access all those special variables 'random_prod_change' : {'type': 'cb', 'num': 0x29, 'flag_bit': 4}, 'monthly_prod_change' : {'type': 'cb', 'num': 0x35, 'flag_bit': 5}, 'cargo_subtype_display' : {'type': 'cb', 'num': 0x37, 'flag_bit': 6}, 'extra_text_fund' : {'type': 'cb', 'num': 0x38, 'flag_bit': 7}, 'extra_text_industry' : {'type': 'cb', 'num': 0x3A, 'flag_bit': 8}, 'control_special' : {'type': 'cb', 'num': 0x3B, 'flag_bit': 9}, 'stop_accept_cargo' : {'type': 'cb', 'num': 0x3D, 'flag_bit': 10}, 'colour' : {'type': 'cb', 'num': 0x14A, 'flag_bit': 11}, 'cargo_input' : {'type': 'cb', 'num': 0x14B, 'flag_bit': 12}, 'cargo_output' : {'type': 'cb', 'num': 0x14C, 'flag_bit': 13}, 'build_prod_change' : {'type': 'cb', 'num': 0x15F, 'flag_bit': 14}, 'default' : {'type': 'cargo', 'num': None}, } # Cargos def cargo_profit_value(value): # In NFO, calculation is (amount * price_factor * cb_result) / 8192 # Units of the NML price factor differ by about 41.12, i.e. 1 NML unit = 41 NFO units # That'd make the formula (amount * price_factor * cb_result) / (8192 / 41) # This is almost (error 0.01%) equivalent to the following, which is what this calculation does # (amount * price_factor * (cb_result * 329 / 256)) / 256 # This allows us to report a factor of 256 in the documentation, which makes a lot more sense than 199.804... # Not doing the division here would improve accuracy, but limits the range of the return value too much value = expression.BinOp(nmlop.MUL, value, expression.ConstantNumeric(329), value.pos) return expression.BinOp(nmlop.DIV, value, expression.ConstantNumeric(256), value.pos) callbacks[0x0B] = { 'profit' : {'type': 'cb', 'num': 0x39, 'flag_bit': 0, 'value_function': cargo_profit_value}, 'station_rating' : {'type': 'cb', 'num': 0x145, 'flag_bit': 1}, 'default' : {'type': 'cargo', 'num': None}, } # Sound effects (0x0C) have no item-specific action3 # Airports callbacks[0x0D] = { 'additional_text' : {'type': 'cb', 'num': 0x155}, 'layout_name' : {'type': 'cb', 'num': 0x156}, 'default' : {'type': 'cargo', 'num': None}, } # New signals (0x0E) have no item-specific action3 # Objects callbacks[0x0F] = { 'tile_check' : {'type': 'cb', 'num': 0x157, 'flag_bit': 0, 'purchase': 2, 'value_function': lambda val: expression.BinOp(nmlop.XOR, val, expression.ConstantNumeric(0x400), val.pos)}, 'anim_next_frame' : {'type': 'cb', 'num': 0x158, 'flag_bit': 1}, 'anim_control' : {'type': 'cb', 'num': 0x159}, 'anim_speed' : {'type': 'cb', 'num': 0x15A, 'flag_bit': 2}, 'colour' : {'type': 'cb', 'num': 0x15B, 'flag_bit': 3}, 'additional_text' : {'type': 'cb', 'num': 0x15C, 'flag_bit': 4, 'purchase': 2}, 'autoslope' : {'type': 'cb', 'num': 0x15D, 'flag_bit': 5}, 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } # Railtypes callbacks[0x10] = { # No default here, it makes no sense 'gui' : {'type': 'cargo', 'num': 0x00}, 'track_overlay' : {'type': 'cargo', 'num': 0x01}, 'underlay' : {'type': 'cargo', 'num': 0x02}, 'tunnels' : {'type': 'cargo', 'num': 0x03}, 'catenary_wire' : {'type': 'cargo', 'num': 0x04}, 'catenary_pylons' : {'type': 'cargo', 'num': 0x05}, 'bridge_surfaces' : {'type': 'cargo', 'num': 0x06}, 'level_crossings' : {'type': 'cargo', 'num': 0x07}, 'depots' : {'type': 'cargo', 'num': 0x08}, 'fences' : {'type': 'cargo', 'num': 0x09}, 'tunnel_overlay' : {'type': 'cargo', 'num': 0x0A}, 'signals' : {'type': 'cargo', 'num': 0x0B}, } # Airport tiles callbacks[0x11] = { 'foundations' : {'type': 'cb', 'num': 0x150, 'flag_bit': 5}, 'anim_control' : {'type': 'cb', 'num': 0x152}, 'anim_next_frame' : {'type': 'cb', 'num': 0x153, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x154, 'flag_bit': 1}, 'default' : {'type': 'cargo', 'num': None}, } nml-0.2.4/nml/actions/action8.py0000644000061700006170000000263012036626442017202 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import grfstrings from nml.actions import base_action class Action8(base_action.BaseAction): def __init__(self, grfid, name, description): self.grfid = grfid self.name = name self.description = description def write(self, file): name = grfstrings.get_translation(self.name) desc = grfstrings.get_translation(self.description) size = 6 + grfstrings.get_string_size(name) + grfstrings.get_string_size(desc) file.start_sprite(size) file.print_bytex(8) file.print_bytex(7) file.print_string(self.grfid.value, False, True) file.print_string(name) file.print_string(desc) file.end_sprite() def skip_action7(self): return False nml-0.2.4/nml/actions/action2var.py0000644000061700006170000012003412036626442017704 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action2, action2real, action2var_variables, action4, action6, actionD from nml import expression, generic, global_constants, nmlop, unit from nml.ast import general class Action2Var(action2.Action2): """ Variational Action2. This is the NFO equivalent of a switch-block in NML. It computes a single integer from one or more variables and picks it's return value based on the result of the computation. The return value can be either a 15bit integer or a reference to another action2. @ivar type_byte: The size (byte, word, double word) and access type (own object or related object). 0x89 (own object, double word) and 0x8A (related object, double word) and the only supported values. @type type_byte: C{int} @ivar ranges: List of return value ranges. Each range contains a minimum and a maximum value and a return value. The list is checked in order, if the result of the computation is between the miminum and maximum (inclusive) of one range the result of that range is returned. The result can be either an integer of another action2. @ivar ranges: C{list} of L{VarAction2Range} """ def __init__(self, feature, name, type_byte): action2.Action2.__init__(self, feature, name) self.type_byte = type_byte self.ranges = [] def resolve_tmp_storage(self): for var in self.var_list: if isinstance(var, VarAction2StoreTempVar): location = self.tmp_locations[0] self.remove_tmp_location(location, False) var.set_register(location) def prepare_output(self): action2.Action2.prepare_output(self) for i in range(0, len(self.var_list) - 1, 2): self.var_list[i].shift |= 0x20 for i in range(0, len(self.var_list), 2): if isinstance(self.var_list[i], VarAction2ProcCallVar): self.var_list[i].resolve_parameter(self.feature) for r in self.ranges: if isinstance(r.result, expression.SpriteGroupRef): r.result = r.result.get_action2_id(self.feature) else: r.result = r.result.value | 0x8000 if isinstance(self.default_result, expression.SpriteGroupRef): self.default_result = self.default_result.get_action2_id(self.feature) else: self.default_result = self.default_result.value | 0x8000 def write(self, file): #type_byte, num_ranges, default_result = 4 #2 bytes for the result, 8 bytes for the min/max range. size = 4 + (2 + 8) * len(self.ranges) for var in self.var_list: if isinstance(var, nmlop.Operator): size += 1 else: size += var.get_size() self.write_sprite_start(file, size) file.print_bytex(self.type_byte) file.newline() for var in self.var_list: if isinstance(var, nmlop.Operator): file.print_bytex(var.act2_num, var.act2_str) else: var.write(file, 4) file.newline(var.comment) file.print_byte(len(self.ranges)) file.newline() for r in self.ranges: file.print_wordx(r.result) file.print_varx(r.min.value, 4) file.print_varx(r.max.value, 4) file.newline(r.comment) file.print_wordx(self.default_result) file.comment(self.default_comment) file.end_sprite() class VarAction2Var(object): """ Represents a variable for use in a (advanced) variational action2. @ivar var_num: Number of the variable to use. @type var_num: C{int} @ivar shift: The number of bits to shift the value of the given variable to the right. @type shift: C{int} @ivar mask: Bitmask to use on the value after shifting it. @type mask: C{int} @ivar parameter: Parameter to be used as argument for the variable. @type parameter: C{int} or C{None} @precondition: (0x60 <= var_num <= 0x7F) == (parameter is not None) @ivar add: If not C{None}, add this value to the result. @type add: C{int} or C{None} @ivar div: If not C{None}, divide the result by this. @type add: C{int} or C{None} @ivar mod: If not C{None}, compute (result module mod). @type add: C{int} or C{None} @ivar comment: Textual description of this variable. @type comment: C{basestr} """ def __init__(self, var_num, shift, mask, parameter = None): self.var_num = var_num self.shift = shift self.mask = mask self.parameter = parameter self.add = None self.div = None self.mod = None self.comment = "" def write(self, file, size): file.print_bytex(self.var_num) if self.parameter is not None: file.print_bytex(self.parameter) if self.mod is not None: self.shift |= 0x80 elif self.add is not None or self.div is not None: self.shift |= 0x40 file.print_bytex(self.shift) file.print_varx(self.mask, size) if self.add is not None: file.print_varx(self.add, size) if self.div is not None: file.print_varx(self.div, size) elif self.mod is not None: file.print_varx(self.mod, size) else: #no div or add, just divide by 1 file.print_varx(1, size) def get_size(self): #var number (1) [+ parameter (1)] + shift num (1) + and mask (4) [+ add (4) + div/mod (4)] size = 6 if self.parameter is not None: size += 1 if self.add is not None or self.div is not None or self.mod is not None: size += 8 return size def supported_by_actionD(self, raise_error): assert not raise_error return False # Class for var 7E procedure calls class VarAction2ProcCallVar(VarAction2Var): def __init__(self, sg_ref): VarAction2Var.__init__(self, 0x7E, 0, 0) # Reference to the called action2 self.sg_ref = sg_ref def resolve_parameter(self, feature): self.parameter = self.sg_ref.get_action2_id(feature) def get_size(self): return 7 def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) # General load and store class for temp parameters # Register is allocated at the store operation class VarAction2StoreTempVar(VarAction2Var): def __init__(self): VarAction2Var.__init__(self, 0x1A, 0, 0) #mask holds the number, it's resolved in Action2Var.resolve_tmp_storage self.load_vars = [] def set_register(self, register): self.mask = register for load_var in self.load_vars: load_var.parameter = register def get_size(self): return 6 def get_mask(size): if size == 1: return 0xFF elif size == 2: return 0xFFFF return 0xFFFFFFFF class VarAction2LoadTempVar(VarAction2Var, expression.Expression): def __init__(self, tmp_var): VarAction2Var.__init__(self, 0x7D, 0, 0) expression.Expression.__init__(self, None) assert isinstance(tmp_var, VarAction2StoreTempVar) tmp_var.load_vars.append(self) def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) def get_size(self): return 7 def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): assert not raise_error return False # Temporary load and store classes used for spritelayout parameters # Register is allocated in a separate entity class VarAction2LayoutParam(object): def __init__(self): self.register = None self.store_vars = [] self.load_vars = [] def set_register(self, register): self.register = register for store_var in self.store_vars: store_var.mask = register for load_var in self.load_vars: load_var.parameter = register class VarAction2LoadLayoutParam(VarAction2Var, expression.Expression): def __init__(self, param): VarAction2Var.__init__(self, 0x7D, 0, 0) expression.Expression.__init__(self, None) assert isinstance(param, VarAction2LayoutParam) param.load_vars.append(self) # Register is stored in parameter def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) def get_size(self): return 7 def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): assert not raise_error return False class VarAction2StoreLayoutParam(VarAction2Var): def __init__(self, param): VarAction2Var.__init__(self, 0x1A, 0, 0) assert isinstance(param, VarAction2LayoutParam) param.store_vars.append(self) # Register is stored in mask def get_size(self): return 6 class VarAction2Range(object): def __init__(self, min, max, result, comment): self.min = min self.max = max self.result = result self.comment = comment class Modification(object): def __init__(self, param, size, offset): self.param = param self.size = size self.offset = offset def pow2(expr): #2**x = (1 ror (32 - x)) if isinstance(expr, expression.ConstantNumeric): return expression.ConstantNumeric(1 << expr.value) expr = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(32), expr) expr = expression.BinOp(nmlop.ROT_RIGHT, expression.ConstantNumeric(1), expr) return expr class Varaction2Parser(object): def __init__(self, feature): self.feature = feature # Depends on feature and var_range self.extra_actions = [] self.mods = [] self.var_list = [] self.var_list_size = 0 self.proc_call_list = [] def preprocess_binop(self, expr): """ Several nml operators are not directly support by nfo so we have to work around that by implementing those operators in terms of others. @return: A pre-processed version of the expression. @rtype: L{Expression} """ assert isinstance(expr, expression.BinOp) if expr.op == nmlop.CMP_LT: #return value is 0, 1 or 2, we want to map 0 to 1 and the others to 0 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) #reduce the problem to 0/1 expr = expression.BinOp(nmlop.MIN, expr, expression.ConstantNumeric(1)) #and invert the result expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_GT: #return value is 0, 1 or 2, we want to map 2 to 1 and the others to 0 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) #subtract one expr = expression.BinOp(nmlop.SUB, expr, expression.ConstantNumeric(1)) #map -1 and 0 to 0 expr = expression.BinOp(nmlop.MAX, expr, expression.ConstantNumeric(0)) elif expr.op == nmlop.CMP_LE: #return value is 0, 1 or 2, we want to map 2 to 0 and the others to 1 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) #swap 0 and 2 expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(2)) #map 1/2 to 1 expr = expression.BinOp(nmlop.MIN, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_GE: #return value is 0, 1 or 2, we want to map 1/2 to 1 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.MIN, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_EQ: #return value is 0, 1 or 2, we want to map 1 to 1, other to 0 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_NEQ: #same as CMP_EQ but invert the result expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.SHIFT_LEFT: #a << b ==> a * (2**b) expr = expression.BinOp(nmlop.MUL, expr.expr1, pow2(expr.expr2)) elif expr.op == nmlop.SHIFT_RIGHT: #a >> b ==> a / (2**b) expr = expression.BinOp(nmlop.DIV, expr.expr1, pow2(expr.expr2)) elif expr.op == nmlop.SHIFTU_RIGHT: #a >>> b ==> (uint)a / (2**b) expr = expression.BinOp(nmlop.DIVU, expr.expr1, pow2(expr.expr2)) elif expr.op == nmlop.HASBIT: # hasbit(x, n) ==> (x >> n) & 1 expr = expression.BinOp(nmlop.DIV, expr.expr1, pow2(expr.expr2)) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.NOTHASBIT: # !hasbit(x, n) ==> ((x >> n) & 1) ^ 1 expr = expression.BinOp(nmlop.DIV, expr.expr1, pow2(expr.expr2)) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(1)) return expr.reduce() def preprocess_ternaryop(self, expr): assert isinstance(expr, expression.TernaryOp) guard = expression.Boolean(expr.guard).reduce() self.parse(guard) if isinstance(expr.expr1, expression.ConstantNumeric) and isinstance(expr.expr2, expression.ConstantNumeric): # This can be done more efficiently as (guard)*(expr1-expr2) + expr2 self.var_list.append(nmlop.MUL) diff_var = VarAction2Var(0x1A, 0, expr.expr1.value - expr.expr2.value) diff_var.comment = "expr1 - expr2" self.var_list.append(diff_var) self.var_list.append(nmlop.ADD) # Add var sizes, +2 for the operators self.var_list_size += 2 + diff_var.get_size() return expr.expr2 else: guard_var = VarAction2StoreTempVar() guard_var.comment = "guard" inverted_guard_var = VarAction2StoreTempVar() inverted_guard_var.comment = "!guard" self.var_list.append(nmlop.STO_TMP) self.var_list.append(guard_var) self.var_list.append(nmlop.XOR) var = VarAction2Var(0x1A, 0, 1) self.var_list.append(var) self.var_list.append(nmlop.STO_TMP) self.var_list.append(inverted_guard_var) self.var_list.append(nmlop.VAL2) # the +4 is for the 4 operators added above (STO_TMP, XOR, STO_TMP, VAL2) self.var_list_size += 4 + guard_var.get_size() + inverted_guard_var.get_size() + var.get_size() expr1 = expression.BinOp(nmlop.MUL, expr.expr1, VarAction2LoadTempVar(guard_var)) expr2 = expression.BinOp(nmlop.MUL, expr.expr2, VarAction2LoadTempVar(inverted_guard_var)) return expression.BinOp(nmlop.ADD, expr1, expr2) def preprocess_storageop(self, expr): assert isinstance(expr, expression.StorageOp) max = 0xF if expr.info['perm'] else 0x10F if isinstance(expr.register, expression.ConstantNumeric) and expr.register.value > max: raise generic.ScriptError("Register number must be in range 0..%d, encountered %d." % (max, expr.register.value), expr.pos) if expr.info['perm'] and self.feature not in (0x08, 0x0A, 0x0D): raise generic.ScriptError("Persistent storage is not supported for feature '%s'" % general.feature_name(self.feature), expr.pos) if expr.info['store']: op = nmlop.STO_PERM if expr.info['perm'] else nmlop.STO_TMP ret = expression.BinOp(op, expr.value, expr.register, expr.pos) else: var_num = 0x7C if expr.info['perm'] else 0x7D ret = expression.Variable(expression.ConstantNumeric(var_num), param=expr.register, pos=expr.pos) if expr.info['perm'] and self.feature == 0x08: # store grfid in register 0x100 for town persistent storage grfid = expression.ConstantNumeric(0xFFFFFFFF if expr.grfid is None else expression.parse_string_to_dword(expr.grfid)) store_op = expression.BinOp(nmlop.STO_TMP, grfid, expression.ConstantNumeric(0x100), expr.pos) ret = expression.BinOp(nmlop.VAL2, store_op, ret, expr.pos) elif expr.grfid is not None: raise generic.ScriptError("Specifying a grfid is only possible for town persistent storage.", expr.pos) return ret def parse_expr_to_constant(self, expr, offset): if isinstance(expr, expression.ConstantNumeric): return expr.value tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) self.mods.append(Modification(tmp_param, 4, self.var_list_size + offset)) return 0 def parse_variable(self, expr): if not isinstance(expr.num, expression.ConstantNumeric): raise generic.ScriptError("Variable number must be a constant number", expr.num.pos) if not (expr.param is None or isinstance(expr.param, expression.ConstantNumeric)): raise generic.ScriptError("Variable parameter must be a constant number", expr.param.pos) if len(expr.extra_params) > 0: first_var = len(self.var_list) == 0 backup_op = None value_backup = None if not first_var: backup_op = self.var_list.pop() value_backup = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(value_backup) self.var_list.append(nmlop.VAL2) self.var_list_size += value_backup.get_size() + 1 #Last value == 0, and this is right before we're going to use #the extra parameters. Set them to their correct value here. for extra_param in expr.extra_params: self.parse(extra_param[1]) self.var_list.append(nmlop.STO_TMP) var = VarAction2Var(0x1A, 0, extra_param[0]) self.var_list.append(var) self.var_list.append(nmlop.VAL2) self.var_list_size += var.get_size() + 2 if not first_var: value_loadback = VarAction2LoadTempVar(value_backup) self.var_list.append(value_loadback) self.var_list.append(backup_op) self.var_list_size += value_loadback.get_size() + 1 if expr.param is None: offset = 2 param = None else: offset = 3 param = expr.param.value mask = self.parse_expr_to_constant(expr.mask, offset) var = VarAction2Var(expr.num.value, expr.shift.value, mask, param) if expr.add is not None: var.add = self.parse_expr_to_constant(expr.add, offset + 4) if expr.div is not None: var.div = self.parse_expr_to_constant(expr.div, offset + 8) if expr.mod is not None: var.mod = self.parse_expr_to_constant(expr.mod, offset + 8) self.var_list.append(var) self.var_list_size += var.get_size() def parse_binop(self, expr): if expr.op.act2_num is None: expr.supported_by_action2(True) if isinstance(expr.expr2, (expression.ConstantNumeric, expression.Variable)) or \ isinstance(expr.expr2, (VarAction2LoadTempVar, VarAction2LoadLayoutParam)) or \ (isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric)) or \ expr.op == nmlop.VAL2: expr2 = expr.expr2 elif expr.expr2.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr.expr2) self.extra_actions.extend(tmp_param_actions) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param)) else: #The expression is so complex we need to compute it first, store the #result and load it back later. self.parse(expr.expr2) tmp_var = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(tmp_var) self.var_list.append(nmlop.VAL2) #the +2 is for both operators self.var_list_size += tmp_var.get_size() + 2 expr2 = VarAction2LoadTempVar(tmp_var) #parse expr1 self.parse(expr.expr1) self.var_list.append(expr.op) self.var_list_size += 1 self.parse(expr2) def parse_constant(self, expr): var = VarAction2Var(0x1A, 0, expr.value) self.var_list.append(var) self.var_list_size += var.get_size() def parse_param(self, expr): self.mods.append(Modification(expr.num.value, 4, self.var_list_size + 2)) var = VarAction2Var(0x1A, 0, 0) var.comment = str(expr) self.var_list.append(var) self.var_list_size += var.get_size() def parse_string(self, expr): str_id, actions = action4.get_string_action4s(0, 0xD0, expr) self.extra_actions.extend(actions) self.parse_constant(expression.ConstantNumeric(str_id)) def parse_via_actionD(self, expr): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) num = expression.ConstantNumeric(tmp_param) self.parse(expression.Parameter(num)) def parse_proc_call(self, expr): assert isinstance(expr, expression.SpriteGroupRef) var_access = VarAction2ProcCallVar(expr) self.var_list.append(var_access) self.var_list_size += var_access.get_size() self.proc_call_list.append(expr) def parse_expr(self, expr): if isinstance(expr, expression.Array): for expr2 in expr.values: self.parse(expr2) self.var_list.append(nmlop.VAL2) self.var_list_size += 1 self.var_list.pop() else: self.parse(expr) def parse(self, expr): #Preprocess the expression if isinstance(expr, expression.SpecialParameter): #do this first, since it may evaluate to a BinOp expr = expr.to_reading() if isinstance(expr, expression.BinOp): expr = self.preprocess_binop(expr) elif isinstance(expr, expression.Boolean): expr = expression.BinOp(nmlop.MINU, expr.expr, expression.ConstantNumeric(1)) elif isinstance(expr, expression.Not): expr = expression.BinOp(nmlop.XOR, expr.expr, expression.ConstantNumeric(1)) elif isinstance(expr, expression.BinNot): expr = expression.BinOp(nmlop.XOR, expr.expr, expression.ConstantNumeric(0xFFFFFFFF)) elif isinstance(expr, expression.TernaryOp) and not expr.supported_by_actionD(False): expr = self.preprocess_ternaryop(expr) elif isinstance(expr, expression.StorageOp): expr = self.preprocess_storageop(expr) #Try to parse the expression to a list of variables+operators if isinstance(expr, expression.ConstantNumeric): self.parse_constant(expr) elif isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): self.parse_param(expr) elif isinstance(expr, expression.Variable): self.parse_variable(expr) elif expr.supported_by_actionD(False): self.parse_via_actionD(expr) elif isinstance(expr, expression.BinOp): self.parse_binop(expr) elif isinstance(expr, expression.String): self.parse_string(expr) elif isinstance(expr, (VarAction2LoadTempVar, VarAction2LoadLayoutParam)): self.var_list.append(expr) self.var_list_size += expr.get_size() else: expr.supported_by_action2(True) assert False #supported_by_action2 should have raised the correct error already def parse_var(info, pos): res = expression.Variable(expression.ConstantNumeric(info['var']), expression.ConstantNumeric(info['start']), expression.ConstantNumeric((1 << info['size']) - 1), None, pos) if 'function' in info: return info['function'](res, info) return res def parse_60x_var(name, args, pos, info): if 'function' in info: # Special function to extract parameters if there is more than one param, extra_params = info['function'](name, args, pos, info) else: # Default function to extract parameters param, extra_params = action2var_variables.default_60xvar(name, args, pos, info) if isinstance(param, expression.ConstantNumeric) and (0 <= param.value <= 255): res = expression.Variable(expression.ConstantNumeric(info['var']), expression.ConstantNumeric(info['start']), \ expression.ConstantNumeric((1 << info['size']) - 1), param, pos) res.extra_params.extend(extra_params) else: # Make use of var 7B to pass non-constant parameters var = expression.Variable(expression.ConstantNumeric(0x7B), expression.ConstantNumeric(info['start']), \ expression.ConstantNumeric((1 << info['size']) - 1), expression.ConstantNumeric(info['var']), pos) var.extra_params.extend(extra_params) # Set the param in the accumulator beforehand res = expression.BinOp(nmlop.VAL2, param, var, pos) if 'value_function' in info: res = info['value_function'](res, info) return res def parse_minmax(value, unit_str, action_list, act6, offset): """ Parse a min or max value in a switch block. @param value: Value to parse @type value: L{Expression} @param unit_str: Unit to use @type unit_str: C{str} or C{None} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @return: A tuple of two values: - The value to use as min/max - Whether the resulting range may need a sanity check @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{bool} """ check_range = True if unit_str is not None: raise generic.ScriptError("Using a unit is in switch-ranges is not (temporarily) not supported", value.pos) elif isinstance(value, expression.ConstantNumeric): result = value elif isinstance(value, expression.Parameter) and isinstance(value.num, expression.ConstantNumeric): act6.modify_bytes(value.num.value, 4, offset) result = expression.ConstantNumeric(0) check_range = False else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(value) action_list.extend(tmp_param_actions) act6.modify_bytes(tmp_param, 4, offset) result = expression.ConstantNumeric(0) check_range = False return (result, check_range) return_action_id = 0 def create_return_action(expr, feature, name, var_range): """ Create a varaction2 to return the computed value @param expr: Expression to return @type expr: L{Expression} @param feature: Feature of the switch-block @type feature: C{int} @param name: Name of the new varaction2 @type name: C{str} @return: A tuple of two values: - Action list to prepend - Reference to the created varaction2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ varact2parser = Varaction2Parser(feature if var_range == 0x89 else action2var_variables.varact2parent_scope[feature]) varact2parser.parse_expr(expr) action_list = varact2parser.extra_actions extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) varaction2 = Action2Var(feature, name, var_range) varaction2.var_list = varact2parser.var_list varaction2.default_result = expression.ConstantNumeric(0) # Bogus result, it's the nvar == 0 that matters varaction2.default_comment = 'Return computed value' ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) return (action_list, ref) def parse_sg_ref_result(result, action_list, parent_action, var_range): """ Parse a result that is a sprite group reference. @param result: Result to parse @type result: L{SpriteGroupRef} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @return: Result to use in the calling varaction2 @rtype: L{SpriteGroupRef} """ if len(result.param_list) == 0: action2.add_ref(result, parent_action) return result # Result is parametrized # Insert an intermediate varaction2 to store expressions in registers var_feature = parent_action.feature if var_range == 0x89 else action2var_variables.varact2parent_scope[parent_action.feature] varact2parser = Varaction2Parser(var_feature) layout = action2.resolve_spritegroup(result.name) for i, param in enumerate(result.param_list): if i > 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(reduce_varaction2_expr(param, var_feature)) varact2parser.var_list.append(nmlop.STO_TMP) store_tmp = VarAction2StoreLayoutParam(layout.register_map[parent_action.feature][i]) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) global return_action_id name = "@return_action_%d" % return_action_id varaction2 = Action2Var(parent_action.feature, name, var_range) return_action_id += 1 varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(result.name, [], result.pos) varaction2.ranges.append(VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, result.name.value)) varaction2.default_result = ref varaction2.default_comment = result.name.value # Add the references as procs, to make sure, that any intermediate registers # are freed at the spritelayout and thus not selected to pass parameters action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) return ref def parse_result(value, action_list, act6, offset, parent_action, none_result, var_range, repeat_result = 1): """ Parse a result (another switch or CB result) in a switch block. @param value: Value to parse @type value: L{Expression} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param none_result: Result to use to return the computed value @type none_result: L{Expression} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @param repeat_result: Repeat any action6 modifying of the next sprite this many times. @type repeat_result: C{int} @return: A tuple of two values: - The value to use as return value - Comment to add to this value @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{str} """ if value is None: comment = "return;" assert none_result is not None if isinstance(none_result, expression.SpriteGroupRef): result = parse_sg_ref_result(none_result, action_list, parent_action, var_range) else: result = none_result elif isinstance(value, expression.SpriteGroupRef): comment = value.name.value + ';' result = parse_sg_ref_result(value, action_list, parent_action, var_range) elif isinstance(value, expression.ConstantNumeric): comment = "return %d;" % value.value result = value elif isinstance(value, expression.String): comment = "return %s;" % str(value) str_id, actions = action4.get_string_action4s(0, 0xD0, value) action_list.extend(actions) result = expression.ConstantNumeric(str_id - 0xD000 + 0x8000) elif value.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expression.BinOp(nmlop.OR, value, expression.ConstantNumeric(0x8000)).reduce()) comment = "return param[%d];" % tmp_param action_list.extend(tmp_param_actions) for i in range(repeat_result): act6.modify_bytes(tmp_param, 2, offset + 2*i) result = expression.ConstantNumeric(0) else: global return_action_id extra_actions, result = create_return_action(value, parent_action.feature, "@return_action_%d" % return_action_id, var_range) return_action_id += 1 action2.add_ref(result, parent_action) action_list.extend(extra_actions) comment = "return %s" % str(value) return (result, comment) def get_feature(switch_block): feature = switch_block.feature_set.copy().pop() if switch_block.var_range == 0x8A: feature = action2var_variables.varact2parent_scope[feature] if feature is None: raise generic.ScriptError("Parent scope for this feature not available, feature: " + str(feature), switch_block.pos) return feature def reduce_varaction2_expr(expr, feature, extra_dicts = []): # 'normal' and 60+x variables to use vars_normal = action2var_variables.varact2vars[feature] vars_60x = action2var_variables.varact2vars60x[feature] # lambda function to convert (value, pos) to a function pointer # since we need the variable name later on, a reverse lookup is needed # TODO pass the function name along to avoid this func60x = lambda value, pos: expression.FunctionPtr(expression.Identifier(generic.reverse_lookup(vars_60x, value), pos), parse_60x_var, value) # make sure, that variables take precedence about global constants / parameters # this way, use the current climate instead of the climate at load time. return expr.reduce(extra_dicts + [(action2var_variables.varact2_globalvars, parse_var), \ (vars_normal, parse_var), \ (vars_60x, func60x)] + \ global_constants.const_list) def parse_varaction2(switch_block): global return_action_id return_action_id = 0 action6.free_parameters.save() act6 = action6.Action6() action_list = action2real.create_spriteset_actions(switch_block) feature = switch_block.feature_set.copy().pop() varaction2 = Action2Var(feature, switch_block.name.value, switch_block.var_range) expr = reduce_varaction2_expr(switch_block.expr, get_feature(switch_block)) offset = 4 #first var parser = Varaction2Parser(get_feature(switch_block)) parser.parse_expr(expr) action_list.extend(parser.extra_actions) for mod in parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + offset) varaction2.var_list = parser.var_list offset += parser.var_list_size + 1 # +1 for the byte num-ranges none_result = None if any(map(lambda x: x is None, [r.result for r in switch_block.body.ranges] + [switch_block.body.default])): # Computed result is returned in at least one result if len(switch_block.body.ranges) == 0: # There is only a default, which is 'return computed result', so we're fine none_result = expression.ConstantNumeric(0) # Return value does not matter else: # Add an extra action to return the computed value extra_actions, none_result = create_return_action(expression.Variable(expression.ConstantNumeric(0x1C)), feature, switch_block.name.value + "@return", 0x89) action_list.extend(extra_actions) if len(switch_block.body.ranges) == 0 and switch_block.body.default is not None: # Computed result is not returned, but there are no ranges # Add one range, to avoid the nvar == 0 bear trap from nml.ast import switch ranges_list = [switch.SwitchRange(expression.ConstantNumeric(0), expression.ConstantNumeric(0), switch_block.body.default)] else: ranges_list = switch_block.body.ranges used_ranges = [] for r in ranges_list: comment = str(r.min) + " .. " + str(r.max) + ": " range_result, range_comment = parse_result(r.result, action_list, act6, offset, varaction2, none_result, switch_block.var_range) comment += range_comment offset += 2 # size of result range_min, check_min = parse_minmax(r.min, r.unit, action_list, act6, offset) offset += 4 range_max, check_max = parse_minmax(r.max, r.unit, action_list, act6, offset) offset += 4 range_overlap = False if check_min and check_max: for existing_range in used_ranges: if existing_range[0] <= range_min.value and range_max.value <= existing_range[1]: generic.print_warning("Range overlaps with existing ranges so it'll never be reached", r.min.pos) range_overlap = True break if not range_overlap: used_ranges.append([range_min.value, range_max.value]) used_ranges.sort() i = 0 while i + 1 < len(used_ranges): if used_ranges[i + 1][0] <= used_ranges[i][1] + 1: used_ranges[i][1] = max(used_ranges[i][1], used_ranges[i + 1][1]) used_ranges.pop(i + 1) else: i += 1 if not range_overlap: varaction2.ranges.append(VarAction2Range(range_min, range_max, range_result, comment)) default, default_comment = parse_result(switch_block.body.default, action_list, act6, offset, varaction2, none_result, switch_block.var_range) varaction2.default_result = default varaction2.default_comment = 'Return computed value' if switch_block.body.default is None else 'default: ' + default_comment if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) switch_block.set_action2(varaction2, feature) action6.free_parameters.restore() return action_list nml-0.2.4/nml/actions/action1.py0000644000061700006170000001661212036626442017200 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action, real_sprite class Action1(base_action.BaseAction): """ Class representing an Action1 @ivar feature: Feature of this action1 @type feature: C{int} @ivar num_sets: Number of (sprite) sets that follow this action 1. @type num_sets: C{int} @ivar num_ent: Number of sprites per set (e.g. (usually) 8 for vehicles) @type num_ent: C{int} """ def __init__(self, feature, num_sets, num_ent): self.feature = feature self.num_sets = num_sets self.num_ent = num_ent def write(self, file): # * 01 file.start_sprite(6) file.print_bytex(1) file.print_bytex(self.feature) file.print_byte(self.num_sets) file.print_varx(self.num_ent, 3) file.newline() file.end_sprite() class SpritesetCollection(base_action.BaseAction): """ A collection that contains multiple spritesets. All spritesets will be written to the same Action1, so they need to have the same number of sprites. @ivar feature: The feature number the action1 will get. @type feature: C{int} @ivar num_sprites_per_spriteset: The number of sprites in each spriteset. @type num_sprites_per_spriteset: C{int} @ivar spritesets: A mapping from spritesets to indices. This allows for quick lookup of whether a spriteset is already in this collection. The indices are unique integers in the range 0 .. len(spritesets) - 1. @type spritesets: C{dict} mapping L{SpriteSet} to C{int}. """ def __init__(self, feature, num_sprites_per_spriteset): self.feature = feature self.num_sprites_per_spriteset = num_sprites_per_spriteset self.spritesets = {} def skip_action7(self): return False def skip_action9(self): return False def skip_needed(self): return False def can_add(self, spritesets, feature): """ Test whether the given list of spritesets can be added to this collection. @param spritesets: The list of spritesets to test for addition. @type spritesets: C{list} of L{SpriteSet} @param feature: The feature of the given spritesets. @type feature: C{int} @return: True iff the given spritesets can be added to this collection. @rtype: C{bool} """ assert len(spritesets) < 0x100 if feature != self.feature: return False for spriteset in spritesets: if len(real_sprite.parse_sprite_list(spriteset.sprite_list, spriteset.pcx)) != self.num_sprites_per_spriteset: return False num_new_sets = len([x for x in spritesets if (x not in self.spritesets)]) return len(self.spritesets) + num_new_sets < 0x100 def add(self, spritesets): """ Add a list of spritesets to this collection. @param spritesets: The list of spritesets to add. @type spritesets: C{list} of L{SpriteSet} @pre: can_add(spritesets, self.feature). """ assert self.can_add(spritesets, self.feature) for spriteset in spritesets: if spriteset not in self.spritesets: self.spritesets[spriteset] = len(self.spritesets) def get_index(self, spriteset): """ Get the index of the given spriteset in the final action1. @param spriteset: The spriteset to get the index of. @type spriteset: L{SpriteSet}. @pre: The spriteset must have been previously added to this collection via #add. """ assert spriteset in self.spritesets return self.spritesets[spriteset] def get_action_list(self): """ Create a list of actions needed to write this collection to the output. This will generate a single Action1 and as many realsprites as needed. @return: A list of actions needed to represet this collection in a GRF. @rtype: C{list} of L{BaseAction} """ actions = [Action1(self.feature, len(self.spritesets), self.num_sprites_per_spriteset)] for idx in range(len(self.spritesets)): for spriteset, spriteset_offset in self.spritesets.iteritems(): if idx == spriteset_offset: actions.extend(real_sprite.parse_sprite_list(spriteset.sprite_list, spriteset.pcx, block_name = spriteset.name)) break return actions """ The collection which was previoulsy used. add_to_action1 will try to reuse this collection as long as possible to reduce the duplication of sprites. As soon as a spriteset with a different feature or amount of sprites is added a new collection will be created. """ last_spriteset_collection = None def add_to_action1(spritesets, feature, pos): """ Add a list of spritesets to a spriteset collection. This will try to reuse one collection as long as possible and create a new one when needed. @param spritesets: List of spritesets that will be used by the next action2. @type spritesets: C{list} of L{SpriteSet} @param feature: Feature of the spritesets. @type feature: C{int} @return: List of collections that needs to be added to the global action list. @rtype: C{list} of L{SpritesetCollection}. """ if not spritesets: return [] setsize = len(real_sprite.parse_sprite_list(spritesets[0].sprite_list, spritesets[0].pcx)) for spriteset in spritesets: if setsize != len(real_sprite.parse_sprite_list(spriteset.sprite_list, spriteset.pcx)): raise generic.ScriptError("Using spritesets with different sizes in a single sprite group / layout is not possible", pos) global last_spriteset_collection actions = [] if last_spriteset_collection is None or not last_spriteset_collection.can_add(spritesets, feature): last_spriteset_collection = SpritesetCollection(feature, len(real_sprite.parse_sprite_list(spritesets[0].sprite_list, spritesets[0].pcx))) actions.append(last_spriteset_collection) last_spriteset_collection.add(spritesets) return actions def get_action1_index(spriteset): """ Get the index of a spriteset in the action1. The given spriteset must have been added in the last call to #add_to_action1. Any new calls to #add_to_action1 may or may not allocate a new spriteset collection and as such make previous spritesets inaccessible. @param spriteset: The spriteset to get the index of. @type spriteset: L{SpriteSet}. @return: The index in the action1 of the given spriteset. @rtype: C{int} """ assert last_spriteset_collection is not None return last_spriteset_collection.get_index(spriteset) nml-0.2.4/nml/actions/action11.py0000644000061700006170000000620512036626442017256 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Action 11 support classes (sounds). """ import os from operator import itemgetter from nml import generic from nml.actions import base_action class Action11(base_action.BaseAction): def __init__(self, num_sounds): self.num_sounds = num_sounds def write(self, file): file.start_sprite(3) file.print_bytex(0x11) file.print_word(self.num_sounds) file.end_sprite() class LoadBinaryFile(base_action.BaseAction): ''' * FF 00 ''' def __init__(self, fname): self.fname = fname self.last = False def prepare_output(self): if not os.access(self.fname, os.R_OK): raise generic.ScriptError("File does not exist.", self.fname) size = os.path.getsize(self.fname) if size == 0: raise generic.ScriptError("Expected a sound file with non-zero length.", self.fname) if size > 0x10000: raise generic.ScriptError("Sound file too big (max 64KB).", self.fname) def write(self, file): file.print_named_filedata(self.fname) if self.last: file.newline() class ImportSound(base_action.BaseAction): """ Import a sound from another grf:: * FE 00 @ivar grfid: ID of the other grf. @type grfid: C{int} @ivar number: Sound number to load. @type number: C{int} """ def __init__(self, grfid, number): self.grfid = grfid self.number = number self.last = False def write(self, file): file.start_sprite(8) file.print_bytex(0xfe) file.print_bytex(0) file.print_dwordx(self.grfid) file.print_wordx(self.number) file.end_sprite() if self.last: file.newline() registered_sounds = {} def add_sound(args): if args not in registered_sounds: registered_sounds[args] = len(registered_sounds) return registered_sounds[args] + 73 def get_sound_actions(): """ Get a list of actions that actually includes all sounds in the output file. """ if not registered_sounds: return [] action_list = [] action_list.append(Action11(len(registered_sounds))) for sound in map(itemgetter(0), sorted(registered_sounds.iteritems(), key=itemgetter(1))): if isinstance(sound, tuple): action_list.append(ImportSound(*sound)) else: action_list.append(LoadBinaryFile(sound)) return action_list nml-0.2.4/nml/actions/action14.py0000644000061700006170000002267012036626441017264 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import grfstrings from nml.actions import base_action class Action14(base_action.BaseAction): def __init__(self, nodes): self.nodes = nodes def skip_action7(self): return False def write(self, file): size = 2 # final 0-byte for node in self.nodes: size += node.get_size() file.start_sprite(size) file.print_bytex(0x14) for node in self.nodes: node.write(file) file.print_bytex(0) file.end_sprite() def split_action14(node, max_size): if node.get_size() <= max_size: return [node, None] if not isinstance(node, BranchNode): return [None, node] new_node = BranchNode(node.id) rest = BranchNode(node.id) copy_to_rest = False for subnode in node.subnodes: if copy_to_rest: rest.subnodes.append(subnode) continue new_subnode, subnode_rest = split_action14(subnode, max_size - new_node.get_size()) if new_subnode is not None: new_node.subnodes.append(new_subnode) if subnode_rest is not None: rest.subnodes.append(subnode_rest) copy_to_rest = True assert len(rest.subnodes) > 0 if len(new_node.subnodes) == 0: return [None, rest] return [new_node, rest] def get_actions(root): action_list = [] while True: node, root = split_action14(root, 65535) assert node is not None action_list.append(Action14([node])) if root is None: break return action_list class Action14Node(object): def __init__(self, type_string, id): self.type_string = type_string self.id = id def get_size(self): """ How many bytes will be written to the output file by L{write}? @return: The size (in bytes) of this node. """ raise NotImplementedError('get_size must be implemented in Action14Node-subclass %r' % type(self)) def write(self, file): """ Write this node to the output file. @param file: The file to write the output to. """ raise NotImplementedError('write must be implemented in Action14Node-subclass %r' % type(self)) def write_type_id(self, file): file.print_string(self.type_string, False, True) if isinstance(self.id, basestring): file.print_string(self.id, False, True) else: file.print_dword(self.id) class TextNode(Action14Node): def __init__(self, id, string, skip_default_langid = False): Action14Node.__init__(self, "T", id) self.string = string grfstrings.validate_string(self.string) self.skip_default_langid = skip_default_langid def get_size(self): if self.skip_default_langid: size = 0 else: size = 6 + grfstrings.get_string_size(grfstrings.get_translation(self.string)) for lang_id in grfstrings.get_translations(self.string): # 6 is for "T" (1), id (4), langid (1) size += 6 + grfstrings.get_string_size(grfstrings.get_translation(self.string, lang_id)) return size def write(self, file): if not self.skip_default_langid: self.write_type_id(file) file.print_bytex(0x7F) file.print_string(grfstrings.get_translation(self.string)) file.newline() for lang_id in grfstrings.get_translations(self.string): self.write_type_id(file) file.print_bytex(lang_id) file.print_string(grfstrings.get_translation(self.string, lang_id)) file.newline() class BranchNode(Action14Node): def __init__(self, id): Action14Node.__init__(self, "C", id) self.subnodes = [] def get_size(self): size = 6 # "C", id, final 0-byte for node in self.subnodes: size += node.get_size() return size def write(self, file): self.write_type_id(file) file.newline() for node in self.subnodes: node.write(file) file.print_bytex(0) file.newline() class BinaryNode(Action14Node): def __init__(self, id, size, val = None): Action14Node.__init__(self, "B", id) self.size = size self.val = val def get_size(self): return 7 + self.size # "B" (1), id (4), size (2), data (self.size) def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_varx(self.val, self.size) file.newline() class UsedPaletteNode(BinaryNode): def __init__(self, pal): BinaryNode.__init__(self, "PALS", 1) self.pal = pal def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_string(self.pal, False, True) file.newline() class BlitterNode(BinaryNode): def __init__(self, blitter): BinaryNode.__init__(self, "BLTR", 1) self.blitter = blitter def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_string(self.blitter, False, True) file.newline() class SettingMaskNode(BinaryNode): def __init__(self, param_num, first_bit, num_bits): BinaryNode.__init__(self, "MASK", 3) self.param_num = param_num self.first_bit = first_bit self.num_bits = num_bits def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_byte(self.param_num) file.print_byte(self.first_bit) file.print_byte(self.num_bits) file.newline() class LimitNode(BinaryNode): def __init__(self, min_val, max_val): BinaryNode.__init__(self, "LIMI", 8) self.min_val = min_val self.max_val = max_val def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_dword(self.min_val) file.print_dword(self.max_val) file.newline() def grf_name_desc_actions(root, name, desc, url, version, min_compatible_version): if len(grfstrings.get_translations(name)) > 0: name_node = TextNode("NAME", name, True) root.subnodes.append(name_node) if len(grfstrings.get_translations(desc)) > 0: desc_node = TextNode("DESC", desc, True) root.subnodes.append(desc_node) if url is not None: desc_node = TextNode("URL_", url) root.subnodes.append(desc_node) version_node = BinaryNode("VRSN", 4, version.value) root.subnodes.append(version_node) min_compatible_version_node = BinaryNode("MINV", 4, min_compatible_version.value) root.subnodes.append(min_compatible_version_node) def param_desc_actions(root, params): num_params = 0 for param_desc in params: num_params += len(param_desc.setting_list) root.subnodes.append(BinaryNode("NPAR", 1, num_params)) param_root = BranchNode("PARA") param_num = 0 setting_num = 0 for param_desc in params: if param_desc.num is not None: param_num = param_desc.num.value for setting in param_desc.setting_list: setting_node = BranchNode(setting_num) if setting.name_string is not None: setting_node.subnodes.append(TextNode("NAME", setting.name_string)) if setting.desc_string is not None: setting_node.subnodes.append(TextNode("DESC", setting.desc_string)) if setting.type == 'int': setting_node.subnodes.append(BinaryNode("MASK", 1, param_num)) min_val = setting.min_val.value if setting.min_val is not None else 0 max_val = setting.max_val.value if setting.max_val is not None else 0xFFFFFFFF setting_node.subnodes.append(LimitNode(min_val, max_val)) if len(setting.val_names) > 0: value_names_node = BranchNode("VALU") for set_val_pair in setting.val_names: value_names_node.subnodes.append(TextNode(set_val_pair[0], set_val_pair[1])) setting_node.subnodes.append(value_names_node) else: assert setting.type == 'bool' setting_node.subnodes.append(BinaryNode("TYPE", 1, 1)) bit = setting.bit_num.value if setting.bit_num is not None else 0 setting_node.subnodes.append(SettingMaskNode(param_num, bit, 1)) if setting.def_val is not None: setting_node.subnodes.append(BinaryNode("DFLT", 4, setting.def_val.value)) param_root.subnodes.append(setting_node) setting_num += 1 param_num += 1 if len(param_root.subnodes) > 0: root.subnodes.append(param_root) def PaletteAction(pal): root = BranchNode("INFO") pal_node = UsedPaletteNode(pal) root.subnodes.append(pal_node) return [Action14([root])] nml-0.2.4/nml/actions/actionE.py0000644000061700006170000000240412036626441017215 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop from nml.actions import base_action, action6, actionD class ActionE(base_action.BaseAction): def __init__(self, grfid_list): self.grfid_list = grfid_list def write(self, file): size = 2 + 4 * len(self.grfid_list) file.start_sprite(size) file.print_bytex(0x0E) file.print_byte(len(self.grfid_list)) for grfid in self.grfid_list: file.newline() file.print_dwordx(grfid) file.newline() file.end_sprite() def parse_deactivate_block(block): return [ActionE(block.grfid_list)] nml-0.2.4/nml/actions/actionB.py0000644000061700006170000001105412036626442017214 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings from nml.actions import base_action, action6, actionD class ActionB(base_action.BaseAction): def __init__(self, severity, lang, msg, data, extra_params): self.severity = severity self.lang = lang self.msg = msg self.data = data self.extra_params = extra_params def write(self, file): size = 4 if not isinstance(self.msg, int): size += grfstrings.get_string_size(self.msg) if self.data is not None: size += grfstrings.get_string_size(self.data) + len(self.extra_params) file.start_sprite(size) file.print_bytex(0x0B) self.severity.write(file, 1) file.print_bytex(self.lang) if isinstance(self.msg, int): file.print_bytex(self.msg) else: file.print_bytex(0xFF) file.print_string(self.msg) if self.data is not None: file.print_string(self.data) for param in self.extra_params: param.write(file, 1) file.newline() file.end_sprite() def skip_action7(self): return False default_error_msg = { 'REQUIRES_TTDPATCH' : 0, 'REQUIRES_DOS_WINDOWS' : 1, 'USED_WITH' : 2, 'INVALID_PARAMETER' : 3, 'MUST_LOAD_BEFORE' : 4, 'MUST_LOAD_AFTER' : 5, 'REQUIRES_OPENTTD' : 6, } error_severity = { 'NOTICE' : 0, 'WARNING' : 1, 'ERROR' : 2, 'FATAL' : 3, } def parse_error_block(error): action6.free_parameters.save() action_list = [] act6 = action6.Action6() if isinstance(error.severity, expression.ConstantNumeric): severity = error.severity elif isinstance(error.severity, expression.Parameter) and isinstance(error.severity.num, expression.ConstantNumeric): act6.modify_bytes(error.severity.num.value, 1, 1) severity = expression.ConstantNumeric(0) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(error.severity) action_list.extend(tmp_param_actions) act6.modify_bytes(tmp_param, 1, 1) severity = expression.ConstantNumeric(0) langs = [0x7F] if isinstance(error.msg, expression.String): custom_msg = True msg_string = error.msg grfstrings.validate_string(msg_string) langs.extend(grfstrings.get_translations(msg_string)) for l in langs: assert l is not None else: custom_msg = False msg = error.msg.reduce_constant().value if error.data is not None: error.data = error.data.reduce() if isinstance(error.data, expression.String): grfstrings.validate_string(error.data) langs.extend(grfstrings.get_translations(error.data)) for l in langs: assert l is not None elif not isinstance(error.data, expression.StringLiteral): raise generic.ScriptError("Error parameter 3 'data' should be the identifier of a custom sting", error.data.pos) params = [] for expr in error.params: if isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): params.append(expr.num) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) action_list.extend(tmp_param_actions) params.append(expression.ConstantNumeric(tmp_param)) langs = list(set(langs)) langs.sort() for lang in langs: if custom_msg: msg = grfstrings.get_translation(msg_string, lang) if error.data is None: data = None elif isinstance(error.data, expression.StringLiteral): data = error.data.value else: data = grfstrings.get_translation(error.data, lang) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionB(severity, lang, msg, data, params)) action6.free_parameters.restore() return action_list nml-0.2.4/nml/actions/action2production.py0000644000061700006170000001336412036626442021311 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action2, action2var, action6, actionD from nml import expression, nmlop class Action2Production(action2.Action2): """ Class corresponding to Action2Industries (=production CB) @ivar version: Production CB version. Version 0 uses constants, version 1 uses registers. @type version: C{int} @ivar sub_in: Amounts (v0) or registers (v1) to subtract from incoming cargos. @type sub_in: C{list} of (C{int} or L{VarAction2Var}) @ivar add_out: Amounts (v0) or registers (v1) to add to the output cargos. @type add_out: C{list} of (C{int} or L{VarAction2Var}) @ivar again: Number (v0) or register (v1), production CB will be run again if nonzero. @type again C{int} or L{VarAction2Var} """ def __init__(self, name, version, sub_in, add_out, again): action2.Action2.__init__(self, 0x0A, name) self.version = version assert version == 0 or version == 1 self.sub_in = sub_in assert len(self.sub_in) == 3 self.add_out = add_out assert len(self.add_out) == 2 self.again = again def prepare_output(self): action2.Action2.prepare_output(self) def write(self, file): cargo_size = 2 if self.version == 0 else 1 size = 2 + 5 * cargo_size action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.version) values = self.sub_in + self.add_out + [self.again] # Read register numbers if needed if self.version == 1: values = [val.parameter for val in values] for val in values[:-1]: file.print_varx(val, cargo_size) file.print_bytex(values[-1]) file.newline() file.end_sprite() def get_production_actions(produce): """ Get the action list that implements the given produce-block in nfo. @param produce: Produce-block to parse. @type produce: L{Produce} """ action_list = [] act6 = action6.Action6() action6.free_parameters.save() result_list = [] varact2parser = action2var.Varaction2Parser(0x0A) if all(map(lambda x: x.supported_by_actionD(False), produce.param_list)): version = 0 for i, param in enumerate(produce.param_list): if isinstance(param, expression.ConstantNumeric): result_list.append(param.value) else: if isinstance(param, expression.Parameter) and isinstance(param.num, expression.ConstantNumeric): param_num = param.num.value else: param_num, tmp_param_actions = actionD.get_tmp_parameter(param) action_list.extend(tmp_param_actions) act6.modify_bytes(param_num, 2 if i < 5 else 1, 1 + 2 * i) result_list.append(0) else: version = 1 for i, param in enumerate(produce.param_list): if isinstance(param, expression.StorageOp) and param.name == 'LOAD_TEMP' and \ isinstance(param.register, expression.ConstantNumeric): # We can load a register directly result_list.append(action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, param.register.value)) else: if len(varact2parser.var_list) != 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(action2var.reduce_varaction2_expr(param, 0x0A)) store_tmp = action2var.VarAction2StoreTempVar() result_list.append(action2var.VarAction2LoadTempVar(store_tmp)) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator if len(act6.modifications) > 0: action_list.append(act6) prod_action = Action2Production(produce.name.value, version, result_list[0:3], result_list[3:5], result_list[5]) action_list.append(prod_action) if len(varact2parser.var_list) == 0: produce.set_action2(prod_action, 0x0A) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var(0x0A, '%s@registers' % produce.name.value, 0x89) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(produce.name, [], None, prod_action) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) produce.set_action2(varaction2, 0x0A) action_list.append(varaction2) action6.free_parameters.restore() return action_list nml-0.2.4/nml/actions/actionA.py0000644000061700006170000000500512036626442017212 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression from nml.actions import base_action, real_sprite, actionD, action6 class ActionA(base_action.BaseAction): """ Action class for Action A (sprite replacement) @ivar sets: List of sprite collections to be replaced. @type sets: C{list} of (C{int}, C{int})-tuples """ def __init__(self, sets): self.sets = sets def write(self, file): # * 0A [ ]+ size = 2 + 3 * len(self.sets) file.start_sprite(size) file.print_bytex(0x0A) file.print_byte(len(self.sets)) for num, first in self.sets: file.print_byte(num) file.print_word(first) file.newline() file.end_sprite() def parse_actionA(replaces): """ Parse replace-block to ActionA. @param replaces: Replace-block to parse. @type replaces: L{ReplaceSprite} """ real_sprite_list = real_sprite.parse_sprite_list(replaces.sprite_list, replaces.pcx, block_name = replaces.name) action_list = [] if isinstance(replaces.start_id, expression.ConstantNumeric): sprite_num = replaces.start_id.value else: action6.free_parameters.save() if isinstance(replaces.start_id, expression.Parameter) and isinstance(replaces.start_id.num, expression.ConstantNumeric): param_num = replaces.start_id.num.value else: param_num, tmp_param_actions = actionD.get_tmp_parameter(replaces.start_id) action_list.extend(tmp_param_actions) act6 = action6.Action6() act6.modify_bytes(param_num, 2, 3) action_list.append(act6) sprite_num = 0 action6.free_parameters.restore() action_list.append(ActionA([(len(real_sprite_list), sprite_num)])) action_list.extend(real_sprite_list) return action_list nml-0.2.4/nml/actions/action0.py0000644000061700006170000005266412036626442017206 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions.action0properties import Action0Property, properties from nml import generic, expression, nmlop, grfstrings from nml.actions import base_action, action4, action6, actionD, actionE, action7 from nml.ast import general # Features that use an extended byte as ID (vehicles, sounds) action0_extended_byte_id = [0, 1, 2, 3, 0x0C] def adjust_value(value, org_value, unit, ottd_convert_func): """ Make sure that the property value written to the NewGRF will match exactly the value as quoted @param value: The value to check, converted to base units @type value: L{Expression} @param org_value: The original value as written in the input file @type org_value: L{Expression} @param unit: The unit of the org_value @type unit: L{Unit} or C{None} @return: The adjusted value @rtype: L{Expression} """ while ottd_convert_func(value, unit) > org_value.value: value = expression.ConstantNumeric(int(value.value - 1), value.pos) while ottd_convert_func(value, unit) < org_value.value: value = expression.ConstantNumeric(int(value.value + 1), value.pos) return value class Action0(base_action.BaseAction): def __init__(self, feature, id): self.feature = feature self.id = id self.prop_list = [] self.num_ids = None def prepare_output(self): if self.num_ids is None: self.num_ids = 1 def write(self, file): size = 7 if self.feature in action0_extended_byte_id else 5 for prop in self.prop_list: size += prop.get_size() file.start_sprite(size) file.print_bytex(0) file.print_bytex(self.feature) file.print_byte(len(self.prop_list)) file.print_bytex(self.num_ids) if self.feature in action0_extended_byte_id: file.print_bytex(0xFF) file.print_wordx(self.id) else: file.print_bytex(self.id) file.newline() for prop in self.prop_list: prop.write(file) file.end_sprite() first_free_id = [116, 88, 11, 41] + 0x0E * [0] def get_free_id(feature): first_free_id[feature] += 1 return first_free_id[feature] - 1 def create_action0(feature, id, act6, action_list): """ Create an action0 with variable id @param feature: Feature of the action0 @type feature: C{int} @param id: ID of the corresponding item @type id: L{Expression} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @return: A tuple of (resulting action0, offset to use for action6) @rtype: C{tuple} of (L{Action0}, C{int}) """ if feature in action0_extended_byte_id: offset = 5 size = 2 else: offset = 4 size = 1 id_val = 0 if isinstance(id, expression.ConstantNumeric): id_val = id.value elif isinstance(id, expression.Parameter) and isinstance(id.num, expression.ConstantNumeric): act6.modify_bytes(id.num.value, size, offset) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) act6.modify_bytes(tmp_param, size, offset) action_list.extend(tmp_param_actions) action0 = Action0(feature, id_val) return (action0, offset + size) def parse_property(feature, name, value, id, unit): """ Parse a single property @param feature: Feature of the associated item @type feature: C{int} @param name: Name (or number) of the property @type name: L{Identifier} or L{ConstantNumeric} @param value: Value of the property @type value: L{Expression} @param id: ID of the associated item @type id: L{Expression} @param unit: Unit of the property value (e.g. km/h) @type unit: L{Unit} or C{None} @return: A tuple containing the following: - List of properties to add to the action 0 - List of actions to prepend - List of modifications to apply via action 6 - List of actions to append @rtype: C{tuple} of (C{list} of L{Action0Property}, C{list} of L{BaseAction}, C{list} of 3-C{tuple}, C{list} of L{BaseAction}) """ global properties prop = None action_list = [] action_list_append = [] mods = [] #Validate feature assert feature in range (0, len(properties)) #guaranteed by item if properties[feature] is None: raise generic.ScriptError("Setting properties for feature '%s' is not possible, no properties are defined." % general.feature_name(feature), name.pos) if isinstance(name, expression.Identifier): if not name.value in properties[feature]: raise generic.ScriptError("Unknown property name: " + name.value, name.pos) prop = properties[feature][name.value] elif isinstance(name, expression.ConstantNumeric): for p in properties[feature]: pdata = properties[feature][p] if 'num' not in pdata or pdata['num'] != name.value: continue prop = pdata if prop is None: raise generic.ScriptError("Unknown property number: " + str(name), name.pos) else: assert False if unit is None or unit.type != 'nfo': # Save the original value to test conversion against it org_value = value mul = 1 if 'unit_conversion' in prop: mul = prop['unit_conversion'] if unit is not None: if not 'unit_type' in prop or unit.type != prop['unit_type']: raise generic.ScriptError("Invalid unit for property: " + str(name), name.pos) mul = mul / unit.convert if mul != 1: if not isinstance(value, (expression.ConstantNumeric, expression.ConstantFloat)): raise generic.ScriptError("Unit conversion specified for property, but no constant value found", value.pos) value = expression.ConstantNumeric(int(value.value * mul + 0.5), value.pos) if unit is not None and 'adjust_value' in prop: value = adjust_value(value, org_value, unit, prop['adjust_value']) if isinstance(value, expression.ConstantFloat): # Always round floats value = expression.ConstantNumeric(int(value.value + 0.5), value.pos) if 'custom_function' in prop: props = prop['custom_function'](value) elif 'string_literal' in prop and (isinstance(value, expression.StringLiteral) or prop['string_literal'] != 4): # Parse non-string exprssions just like integers. User will have to take care of proper value. # This can be used to set a label (=string of length 4) to the value of a parameter. if not isinstance(value, expression.StringLiteral): raise generic.ScriptError("Value for property %d must be a string literal" % prop['num'], value.pos) if len(value.value) != prop['string_literal']: raise generic.ScriptError("Value for property %d must be of length %d" % (prop['num'], prop['string_literal']), value.pos) props = [Action0Property(prop['num'], value, prop['size'])] else: if isinstance(value, expression.ConstantNumeric): pass elif isinstance(value, expression.Parameter) and isinstance(value.num, expression.ConstantNumeric): mods.append((value.num.value, prop['size'], 1)) value = expression.ConstantNumeric(0) elif isinstance(value, expression.String): if not 'string' in prop: raise generic.ScriptError("String used as value for non-string property: " + str(prop['num']), value.pos) string_range = prop['string'] stringid, string_actions = action4.get_string_action4s(feature, string_range, value, id) value = expression.ConstantNumeric(stringid) action_list_append.extend(string_actions) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(value) mods.append((tmp_param, prop['size'], 1)) action_list.extend(tmp_param_actions) value = expression.ConstantNumeric(0) if prop['num'] != -1: props = [Action0Property(prop['num'], value, prop['size'])] else: props = [] if 'append_function' in prop: props.extend(prop['append_function'](value)) return (props, action_list, mods, action_list_append) def parse_property_block(prop_list, feature, id): action6.free_parameters.save() action_list = [] action_list_append = [] act6 = action6.Action6() action0, offset = create_action0(feature, id, act6, action_list) for prop in prop_list: properties, extra_actions, mods, extra_append_actions = parse_property(feature, prop.name, prop.value, id, prop.unit) action_list.extend(extra_actions) action_list_append.extend(extra_append_actions) for mod in mods: act6.modify_bytes(mod[0], mod[1], mod[2] + offset) for p in properties: offset += p.get_size() action0.prop_list.extend(properties) if len(act6.modifications) > 0: action_list.append(act6) if len(action0.prop_list) != 0: action_list.append(action0) action_list.extend(action_list_append) action6.free_parameters.restore() return action_list class IDListProp(object): def __init__(self, prop_num, id_list): self.prop_num = prop_num self.id_list = id_list def write(self, file): file.print_bytex(self.prop_num) for i, id_val in enumerate(self.id_list): if i > 0 and i % 5 == 0: file.newline() file.print_string(id_val.value, False, True) file.newline() def get_size(self): return len(self.id_list) * 4 + 1 def get_cargolist_action(cargo_list): action0 = Action0(0x08, 0) action0.prop_list.append(IDListProp(0x09, cargo_list)) action0.num_ids = len(cargo_list) return [action0] def get_railtypelist_action(railtype_list): action6.free_parameters.save() act6 = action6.Action6() action_list = [] action0 = Action0(0x08, 0) id_table = [] offset = 2 for railtype in railtype_list: offset += 4 if isinstance(railtype, expression.StringLiteral): id_table.append(railtype) continue param, extra_actions = actionD.get_tmp_parameter(expression.ConstantNumeric(expression.parse_string_to_dword(railtype[-1]))) action_list.extend(extra_actions) for idx in range(len(railtype)-2, -1, -1): val = expression.ConstantNumeric(expression.parse_string_to_dword(railtype[idx])) action_list.append(action7.SkipAction(0x09, 0x00, 4, (0x0D, None), val.value, 1)) action_list.append(actionD.ActionD(expression.ConstantNumeric(param), expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0xFF), val)) act6.modify_bytes(param, 4, offset) id_table.append(expression.StringLiteral(r"\00\00\00\00", None)) action0.prop_list.append(IDListProp(0x12, id_table)) action0.num_ids = len(railtype_list) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(action0) action6.free_parameters.restore() return action_list class ByteListProp(object): def __init__(self, prop_num, data): self.prop_num = prop_num self.data = data def write(self, file): file.print_bytex(self.prop_num) file.newline() for i, data_val in enumerate(self.data): if i > 0 and i % 8 == 0: file.newline() file.print_bytex(ord(data_val)) file.newline() def get_size(self): return len(self.data) + 1 def get_snowlinetable_action(snowline_table): assert(len(snowline_table) == 12*32) action6.free_parameters.save() action_list = [] tmp_param_map = {} #Cache for tmp parameters act6 = action6.Action6() act0 = Action0(0x08, 0) act0.num_ids = 1 data_table = [] idx = 0 while idx < len(snowline_table): val = snowline_table[idx] if isinstance(val, expression.ConstantNumeric): data_table.append(val.value) idx += 1 continue if idx + 3 >= len(snowline_table): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(val) tmp_param_map[val] = tmp_param act6.modify_bytes(tmp_param, 1, 6 + idx) action_list.extend(tmp_param_actions) data_table.append(0) idx += 1 continue # Merge the next 4 values together in a single parameter. val2 = expression.BinOp(nmlop.SHIFT_LEFT, snowline_table[idx + 1], expression.ConstantNumeric(8)) val3 = expression.BinOp(nmlop.SHIFT_LEFT, snowline_table[idx + 2], expression.ConstantNumeric(16)) val4 = expression.BinOp(nmlop.SHIFT_LEFT, snowline_table[idx + 3], expression.ConstantNumeric(24)) expr = expression.BinOp(nmlop.OR, val, val2) expr = expression.BinOp(nmlop.OR, expr, val3) expr = expression.BinOp(nmlop.OR, expr, val4) expr = expr.reduce() #Cache lookup, saves some ActionDs if expr in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[expr], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) tmp_param_map[expr] = tmp_param act6.modify_bytes(tmp_param, 4, 6 + idx) action_list.extend(tmp_param_actions) data_table.extend([0, 0, 0, 0]) idx += 4 act0.prop_list.append(ByteListProp(0x10, ''.join([chr(x) for x in data_table]))) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) action6.free_parameters.restore() return action_list def get_basecost_action(basecost): action6.free_parameters.save() action_list = [] tmp_param_map = {} #Cache for tmp parameters #We want to avoid writing lots of action0s if possible i = 0 while i < len(basecost.costs): cost = basecost.costs[i] act6 = action6.Action6() act0, offset = create_action0(0x08, cost.name, act6, action_list) first_id = cost.name.value if isinstance(cost.name, expression.ConstantNumeric) else None num_ids = 1 #Number of values that will be written in one go values = [] #try to capture as much values as possible while True: cost = basecost.costs[i] if isinstance(cost.value, expression.ConstantNumeric): values.append(cost.value) else: #Cache lookup, saves some ActionDs if cost.value in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[cost.value], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(cost.value) tmp_param_map[cost.value] = tmp_param act6.modify_bytes(tmp_param, 1, offset + num_ids) action_list.extend(tmp_param_actions) values.append(expression.ConstantNumeric(0)) #check if we can append the next to this one (it has to be consecutively numbered) if first_id is not None and (i + 1) < len(basecost.costs): nextcost = basecost.costs[i+1] if isinstance(nextcost.name, expression.ConstantNumeric) and nextcost.name.value == first_id + num_ids: num_ids += 1 i += 1 #Yes We Can, continue the loop to append this value to the list and try further continue # No match, so stop it and write an action0 break act0.prop_list.append(Action0Property(0x08, values, 1)) act0.num_ids = num_ids if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) i += 1 action6.free_parameters.restore() return action_list class LanguageTranslationTable(object): def __init__(self, num, name_list, extra_names): self.num = num self.mappings = [] for name, idx in name_list.iteritems(): self.mappings.append( (idx, name) ) if name in extra_names: for extra_name in extra_names[name]: self.mappings.append( (idx, extra_name) ) def write(self, file): file.print_bytex(self.num) for mapping in self.mappings: file.print_bytex(mapping[0]) file.print_string(mapping[1]) file.print_bytex(0) file.newline() def get_size(self): size = 2 for mapping in self.mappings: size += 1 + grfstrings.get_string_size(mapping[1]) return size def get_language_translation_tables(lang): action0 = Action0(0x08, lang.langid) if lang.genders is not None: action0.prop_list.append(LanguageTranslationTable(0x13, lang.genders, lang.gender_map)) if lang.cases is not None: action0.prop_list.append(LanguageTranslationTable(0x14, lang.cases, lang.case_map)) if lang.plural is not None: action0.prop_list.append(Action0Property(0x15, expression.ConstantNumeric(lang.plural), 1)) if len(action0.prop_list) > 0: return [action0] return [] disable_info = { # Vehicles: set climates_available to 0 0x00 : {'num': 116, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, 0x01 : {'num': 88, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, 0x02 : {'num': 11, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, 0x03 : {'num': 41, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, # Houses / industries / airports: Set substitute_type to FF 0x07 : {'num': 110, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}]}, 0x0A : {'num': 37, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}]}, 0x0D : {'num': 10, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}]}, # Cargos: Set bitnum to FF and label to 0 0x0B : {'num': 27, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}, {'num': 0x17, 'size': 4, 'value': 0}]}, } def get_disable_actions(disable): """ Get the action list for a disable_item block @param disable: Disable block @type disable: L{DisableItem} @return: A list of resulting actions @rtype: C{list} of L{BaseAction} """ feature = disable.feature.value if feature not in disable_info: raise generic.ScriptError("disable_item() is not available for feature %d." % feature, disable.pos) if disable.first_id is None: # No ids set -> disable all assert disable.last_id is None first = 0 num = disable_info[feature]['num'] else: first = disable.first_id.value if disable.last_id is None: num = 1 else: num = disable.last_id.value - first + 1 act0 = Action0(feature, first) act0.num_ids = num for prop in disable_info[feature]['props']: act0.prop_list.append(Action0Property(prop['num'], num * [expression.ConstantNumeric(prop['value'])], prop['size'])) return [act0] class EngineOverrideProp(object): def __init__(self, source, target): self.source = source self.target = target def write(self, file): file.print_bytex(0x11) file.print_dwordx(self.source) file.print_dwordx(self.target) file.newline() def get_size(self): return 9 def get_engine_override_action(override): act0 = Action0(0x08, 0) act0.num_ids = 1 act0.prop_list.append(EngineOverrideProp(override.source_grfid, override.grfid)) return [act0] def parse_sort_block(feature, vehid_list): prop_num = [0x1A, 0x20, 0x1B, 0x1B] action_list = [] last = vehid_list[0] idx = len(vehid_list) - 1 while idx >= 0: cur = vehid_list[idx] prop = Action0Property(prop_num[feature], [last], 3) action_list.append(Action0(feature, cur.value)) action_list[-1].prop_list.append(prop) last = cur idx -= 1 return action_list def get_callback_flags_actions(feature, id, flags): """ Get a list of actions to set the callback flags of a certain item @param feature: Feature of the item @type feature: C{int} @param id: ID of the item @type id: L{Expression} @param flags: Value of the 'callback_flags' property @type flags: C{int} @return: A list of actions @rtype: C{list} of L{BaseAction} """ action_list = [] act6 = action6.Action6() act0, offset = create_action0(feature, id, act6, action_list) act0.num_ids = 1 assert 'callback_flags' in properties[feature] props, extra_actions, mods, extra_append_actions = parse_property(feature, expression.Identifier('callback_flags'), expression.ConstantNumeric(flags), id, None) act0.prop_list.extend(props) action_list.extend(extra_actions) for mod in mods: act6.modify_bytes(mod[0], mod[1], mod[2] + offset) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) action_list.extend(extra_append_actions) return action_list nml-0.2.4/nml/actions/action2.py0000644000061700006170000004345112036626442017202 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action from nml.ast import base_statement, general import nml free_action2_ids = list(range(1, 255)) class Action2(base_action.BaseAction): """ Abstract Action2 base class. @ivar name; Name of the action2. @type name: C{str} @ivar feature: Action2 feature byte. @type feature: C{int} @ivar num_refs: Number of references to this action2. @type num_refs: C{int} @ivar id: Number of this action2. @type id: C{int}, or C{None} if no number is allocated yet. @ivar references: All Action2s that are referenced by this Action2. @type references: C{list} of L{Action2Reference} @ivar tmp_locations: List of address in the temporary storage that are free to be used in this varaction2. @type tmp_locations: C{list} of C{int} """ def __init__(self, feature, name): self.feature = feature self.name = name self.num_refs = 0 self.id = None self.references = [] #0x00 - 0x7F: available to user #0x80 - 0xFF: used by NML #0x100 - 0x10F: Special meaning (used for some CB results) self.tmp_locations = list(range(0x80, 0x100)) def prepare_output(self): free_references(self) if self.num_refs == 0: self.id = free_action2_ids[0] else: try: self.id = free_action2_ids.pop() except IndexError: raise generic.ScriptError("Unable to allocate ID for [random]switch, sprite set/layout/group or produce-block. Try reducing the number of such blocks.") def write_sprite_start(self, file, size): assert self.num_refs == 0, "Action2 reference counting has %d dangling references." % self.num_refs file.comment("Name: " + self.name) file.start_sprite(size + 3) file.print_bytex(2) file.print_bytex(self.feature) file.print_bytex(self.id) def skip_action7(self): return False def skip_action9(self): return False def skip_needed(self): return False def remove_tmp_location(self, location, force_recursive): """ Recursively remove a location from the list of available temporary storage locations. It is not only removed from the the list of the current Action2Var but also from all Action2Var it calls. If an Action2Var is referenced as a procedure call, the location is always removed recursively, otherwise only if force_recursive is True. @param location: Number of the storage register to remove. @type location: C{int} @param force_recursive: Force removing this location recursively, also for 'chained' action2s. @type force_recursive: C{bool} """ if location in self.tmp_locations: self.tmp_locations.remove(location) for act2_ref in self.references: if force_recursive or act2_ref.is_proc: act2_ref.action2.remove_tmp_location(location, True) class Action2Reference: """ Container class to store information about an action2 reference @ivar action2: The target action2 @type action2: L{Action2} @ivar is_proc: Whether this reference is made because of a procedure call @type is_proc: C{bool} """ def __init__(self, action2, is_proc): self.action2 = action2 self.is_proc = is_proc def add_ref(ref, source_action, reference_as_proc = False): """ Add a reference to a certain action2. This is needed so we can correctly reserve / free action2 IDs later on. To be called when creating the actions from the AST. @param ref: Reference to the sprite group that corresponds to the action2. @type ref: L{SpriteGroupRef} @param source_action: Source action (act2 or act3) that contains the reference. @type source_action: L{Action2} or L{Action3} @param reference_as_proc: True iff the reference source is a procedure call, which needs special precautions for temp registers. @type reference_as_proc: C{bool} """ # Add reference to list of references of the source action if ref.name.value == 'CB_FAILED': return act2 = ref.act2 if ref.act2 is not None else resolve_spritegroup(ref.name).get_action2(source_action.feature) source_action.references.append(Action2Reference(act2, reference_as_proc)) act2.num_refs += 1 def free_references(source_action): """ Free all references to other action2s from a certain action 2/3 @param source_action: Action that contains the reference @type source_action: L{Action2} or L{Action3} """ for act2_ref in source_action.references: act2 = act2_ref.action2 act2.num_refs -= 1 if act2.num_refs == 0: free_action2_ids.append(act2.id) # Features using sprite groups directly: vehicles, canals, cargos, railtypes, airports features_sprite_group = [0x00, 0x01, 0x02, 0x03, 0x05, 0x0B, 0x0D, 0x10] # Features using sprite layouts: stations, houses, industry tiles, objects and airport tiles features_sprite_layout = [0x04, 0x07, 0x09, 0x0F, 0x11] # All features that need sprite sets features_sprite_set = features_sprite_group + features_sprite_layout def make_sprite_group_class(cls_is_spriteset, cls_is_referenced, cls_has_explicit_feature, cls_is_relocatable = False): """ Metaclass factory which makes base classes for all nodes 'Action 2 graph' This graph is made up of all blocks that are eventually compiled to Action2, which use the same name space. Spritesets do inherit from this class to make referencing them possible, but they are not part of the refernce graph that is built. @param cls_is_spriteset: Whether this class represents a spriteset @type cls_is_spriteset: C{bool} @param cls_is_referenced: True iff this node can be referenced by other nodes @type cls_is_referenced: C{bool} @param cls_has_explicit_feature: Whether the feature of an instance is explicitly set, or derived from nodes that link to it. @type cls_has_explicit_feature: C{bool} @param cls_is_relocatable: Whether instances of this class can be freely moved around or whether they need to to be converted to nfo code at the same location as they are in the nml code. @type cls_is_relocatable: C{bool} @return: The constructed class @rtype: C{type} """ #without either references or an explicit feature, we have nothing to base our feature on assert cls_is_referenced or cls_has_explicit_feature class ASTSpriteGroup(base_statement.BaseStatement): """ Abstract base class for all AST nodes that represent a sprite group This handles all the relations between the various nodes Child classes should do the following: - Implement their own __init__ method - Call BaseStatement.__init__ - Call initialize, pre_process and perpare_output (in that order) - Implement collect_references - Call set_action2 after generating the corresponding action2 (if applicable) @ivar _referencing_nodes: Set of nodes that refer to this node @type _referencing_nodes: C{set} @ivar _referenced_nodes: Set of nodes that this node refers to @type _referenced_nodes: C{set} @ivar _prepared: True iff prepare_output has already been executed @type _prepared: C{bool} @ivar _action2: Mapping of features to action2s @type _action2: C{dict} that maps C{int} to L{Action2} @ivar feature_set: Set of features that use this node @type feature_set: C{set} of C{int} @ivar name: Name of this node, as declared by the user @type name: L{Identifier} @ivar num_params: Number of parameters that can be (and have to be) passed @type num_params: C{int} @ivar used_sprite_sets: List of sprite sets used by this node @type used_sprite_sets: C{list} of L{SpriteSet} """ def __init__(self): """ Subclasses should implement their own __init__ method. This method should not be called, because calling a method on a meta class can be troublesome. Instead, call initialize(..). """ raise NotImplementedError('__init__ must be implemented in ASTSpriteGroup-subclass %r, initialize(..) should be called instead' % type(self)) def initialize(self, name = None, feature = None, num_params = 0): """ Initialize this instance. This function is generally, but not necessarily, called from the child class' constructor. Calling it later (during pre-processing) is also possible, as long as it's called before any other actions are done. @param name: Name of this node, as set by the user (if applicable) Should be be set (not None) iff cls_is_referenced is True @type name: L{Identifier} or C{None} if N/A @param feature: Feature of this node, if set by the user. Should be set (not None) iff cls_has_explicit_feature is True @type feature: C{int} or C{None} """ assert not (self._has_explicit_feature() and feature is None) assert not (cls_is_referenced and name is None) self._referencing_nodes = set() self._referenced_nodes = set() self._prepared = False self._action2 = {} self.feature_set = set([feature]) if feature is not None else set() self.name = name self.num_params = num_params self.used_sprite_sets = [] def register_names(self): if cls_is_relocatable and cls_is_referenced: register_spritegroup(self) def pre_process(self): """ Pre-process this node. During this stage, the reference graph is built. """ refs = self.collect_references() for ref in refs: self._add_reference(ref) if (not cls_is_relocatable) and cls_is_referenced: register_spritegroup(self) def prepare_output(self): """ Prepare this node for outputting. This sets the feature and makes sure it is correct. @return: True iff parsing of this node is needed @rtype: C{bool} """ if not cls_is_referenced: return True if not self._prepared: self._prepared = True # copy, since we're going to modify ref_nodes = self._referencing_nodes.copy() for node in ref_nodes: used = node.prepare_output() if not used: node._remove_reference(self) # now determine the feature if self._has_explicit_feature(): # by this time, feature should be set assert len(self.feature_set) == 1 for n in self._referencing_nodes: if n.feature_set != self.feature_set: raise generic.ScriptError("Cannot refer to block '%s' with feature '%s', expected feature is '%s'" % \ (self.name.value, general.feature_name(self.feature_set.copy().pop()), general.feature_name(n.feature_set.difference(self.feature_set).pop())), n.pos) elif len(self._referencing_nodes) != 0: for n in self._referencing_nodes: # Add the features from all calling blocks to the set self.feature_set.update(n.feature_set) if len(self._referencing_nodes) == 0: # if we can be 'not used', there ought to be a way to refer to this block assert self.name is not None generic.print_warning("Block '%s' is not referenced, ignoring." % self.name.value, self.pos) return len(self._referencing_nodes) != 0 def referenced_nodes(self): """ Get the nodes that this node refers to. @note: Make sure to sort this in a deterministic way when the order of items affects the output. @return: A set of nodes @rtype: C{set} of L{ASTSpriteGroup} """ return self._referenced_nodes def referencing_nodes(self): """ Get the nodes that refer to this node. @note: Make sure to sort this in a deterministic way when the order of items affects the output. @return: A set of nodes @rtype: C{set} of L{ASTSpriteGroup} """ return self._referencing_nodes def collect_references(self): """ This function should collect all references to other nodes from this instance. @return: A collection containing all links to other nodes. @rtype: C{iterable} of L{SpriteGroupRef} """ raise NotImplementedError('collect_references must be implemented in ASTSpriteGroup-subclass %r' % type(self)) def set_action2(self, action2, feature): """ Set this node's resulting action2 @param feature: Feature of the Action2 @type feature: C{int} @param action2: Action2 to set @type action2: L{Action2} """ assert feature not in self._action2 self._action2[feature] = action2 def get_action2(self, feature): """ Get this node's resulting action2 @param feature: Feature of the Action2 @type feature: C{int} @return: Action2 to get @rtype: L{Action2} """ assert feature in self._action2 return self._action2[feature] def has_action2(self, feature): """ Check, if this node already has an action2 for a given feature @param feature: Feature to check @type feature: C{int} @return: True iff there is an action2 for this feature @rtype: C{bool} """ return feature in self._action2 def _add_reference(self, target_ref): """ Add a reference from C{self} to a target with a given name. @param target_ref: Name of the reference target @type target_ref: L{SpriteGroupRef} """ if target_ref.name.value == "CB_FAILED": return target = resolve_spritegroup(target_ref.name) if target.is_spriteset(): assert target.num_params == 0 # Referencing a spriteset directly from graphics/[random]switch # Passing parameters is not possible here if len(target_ref.param_list) != 0: raise generic.ScriptError("Passing parameters to '%s' is only possible from a spritelayout." % target_ref.name.value, target_ref.pos) self.used_sprite_sets.append(target) else: if len(target_ref.param_list) != target.num_params: raise generic.ScriptError("'%s' expects %d parameters, encountered %d." % (target_ref.name.value, target.num_params, len(target_ref.param_list)), target_ref.pos) self._referenced_nodes.add(target) target._referencing_nodes.add(self) def _remove_reference(self, target): """ Add a reference from C{self} to a target @param target: Existing reference target to be removed @type target: L{ASTSpriteGroup} """ assert target in self._referenced_nodes assert self in target._referencing_nodes self._referenced_nodes.remove(target) target._referencing_nodes.remove(self) #Make metaclass arguments available outside of the class def is_spriteset(self): return cls_is_spriteset def _has_explicit_feature(self): return cls_has_explicit_feature return ASTSpriteGroup #list of all registered sprite sets and sprite groups spritegroup_list = {} def register_spritegroup(spritegroup): """ Register a sprite group, so it can be resolved by name later @param spritegroup: Sprite group to register @type spritegroup: L{ASTSpriteGroup} """ name = spritegroup.name.value if name in spritegroup_list: raise generic.ScriptError("Block with name '%s' has already been defined" % name, spritegroup.pos) spritegroup_list[name] = spritegroup nml.global_constants.spritegroups[name] = name def resolve_spritegroup(name): """ Resolve a sprite group with a given name @param name: Name of the sprite group. @type name: L{Identifier} @return: The sprite group that the name refers to. @rtype: L{ASTSpriteGroup} """ if name.value not in spritegroup_list: raise generic.ScriptError("Unknown identifier encountered: '%s'" % name.value, name.pos) return spritegroup_list[name.value] nml-0.2.4/nml/actions/real_sprite.py0000644000061700006170000004041412036626442020150 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression from nml.actions import base_action from nml.ast import assignment import os, Image palmap_d2w = [ 0, 215, 216, 136, 88, 106, 32, 33, # 0..7 40, 245, 10, 11, 12, 13, 14, 15, # 8..15 16, 17, 18, 19, 20, 21, 22, 23, # 16..23 24, 25, 26, 27, 28, 29, 30, 31, # 24..31 53, 54, 34, 35, 36, 37, 38, 39, # 32..39 178, 41, 42, 43, 44, 45, 46, 47, # 40..47 48, 49, 50, 51, 52, 53, 54, 55, # 48..55 56, 57, 58, 59, 60, 61, 62, 63, # 56..63 64, 65, 66, 67, 68, 69, 70, 71, # 64..71 72, 73, 74, 75, 76, 77, 78, 79, # 72..79 80, 81, 82, 83, 84, 85, 86, 87, # 80..87 96, 89, 90, 91, 92, 93, 94, 95, # 88..95 96, 97, 98, 99, 100, 101, 102, 103, # 96..103 104, 105, 53, 107, 108, 109, 110, 111, # 104..111 112, 113, 114, 115, 116, 117, 118, 119, # 112..119 120, 121, 122, 123, 124, 125, 126, 127, # 120..127 128, 129, 130, 131, 132, 133, 134, 135, # 128..135 170, 137, 138, 139, 140, 141, 142, 143, # 136..143 144, 145, 146, 147, 148, 149, 150, 151, # 144..151 152, 153, 154, 155, 156, 157, 158, 159, # 152..159 160, 161, 162, 163, 164, 165, 166, 167, # 160..167 168, 169, 170, 171, 172, 173, 174, 175, # 168..175 176, 177, 178, 179, 180, 181, 182, 183, # 176..183 184, 185, 186, 187, 188, 189, 190, 191, # 184..191 192, 193, 194, 195, 196, 197, 198, 199, # 192..199 200, 201, 202, 203, 204, 205, 206, 207, # 200..207 208, 209, 210, 211, 212, 213, 214, 215, # 208..215 216, 217, 246, 247, 248, 249, 250, 251, # 216..223 252, 253, 254, 227, 228, 229, 230, 231, # 224..231 232, 233, 234, 235, 236, 237, 238, 239, # 232..239 240, 241, 242, 243, 244, 217, 218, 219, # 240..247 220, 221, 222, 223, 224, 225, 226, 255, # 248..255 ] palmap_w2d = [ 0, 1, 2, 3, 4, 5, 6, 7, # 0..7 8, 9, 10, 11, 12, 13, 14, 15, # 8..15 16, 17, 18, 19, 20, 21, 22, 23, # 16..23 24, 25, 26, 27, 28, 29, 30, 31, # 24..31 6, 7, 34, 35, 36, 37, 38, 39, # 32..39 8, 41, 42, 43, 44, 45, 46, 47, # 40..47 48, 49, 50, 51, 52, 53, 54, 55, # 48..55 56, 57, 58, 59, 60, 61, 62, 63, # 56..63 64, 65, 66, 67, 68, 69, 70, 71, # 64..71 72, 73, 74, 75, 76, 77, 78, 79, # 72..79 80, 81, 82, 83, 84, 85, 86, 87, # 80..87 4, 89, 90, 91, 92, 93, 94, 95, # 88..95 96, 97, 98, 99, 100, 101, 102, 103, # 96..103 104, 105, 5, 107, 108, 109, 110, 111, # 104..111 112, 113, 114, 115, 116, 117, 118, 119, # 112..119 120, 121, 122, 123, 124, 125, 126, 127, # 120..127 128, 129, 130, 131, 132, 133, 134, 135, # 128..135 3, 137, 138, 139, 140, 141, 142, 143, # 136..143 144, 145, 146, 147, 148, 149, 150, 151, # 144..151 152, 153, 154, 155, 156, 157, 158, 159, # 152..159 160, 161, 162, 163, 164, 165, 166, 167, # 160..167 168, 169, 170, 171, 172, 173, 174, 175, # 168..175 176, 177, 178, 179, 180, 181, 182, 183, # 176..183 184, 185, 186, 187, 188, 189, 190, 191, # 184..191 192, 193, 194, 195, 196, 197, 198, 199, # 192..199 200, 201, 202, 203, 204, 205, 206, 207, # 200..207 208, 209, 210, 211, 212, 213, 214, 1, # 208..215 2, 245, 246, 247, 248, 249, 250, 251, # 216..223 252, 253, 254, 229, 230, 231, 227, 228, # 224..231 232, 233, 234, 235, 236, 237, 238, 239, # 232..239 240, 241, 242, 243, 244, 9, 218, 219, # 240..247 220, 221, 222, 223, 224, 225, 226, 255, # 248..255 ] def convert_palette(pal): ret = 256 * [0] for idx, colour in enumerate(pal): if 0xD7 <= idx <= 0xE2: if idx != colour: raise generic.ScriptError("Indices 0xD7..0xE2 are not allowed in recolour sprites when the output is in the WIN palette") continue ret[palmap_d2w[idx]] = palmap_d2w[colour] return ret class RealSprite(object): def __init__(self, param_list = None, label = None): self.param_list = param_list self.label = label self.is_empty = False self.xpos = None self.ypos = None self.xsize = None self.ysize = None def debug_print(self, indentation): print indentation*' ' + 'Real sprite, parameters:' for param in self.param_list: param.debug_print(indentation + 2) def get_labels(self): labels = {} if self.label is not None: labels[self.label.value] = 0 return labels, 1 def check_sprite_size(self): generic.check_range(self.xpos.value, 0, 0x7fffFFFF, "Real sprite paramater 'xpos'", self.xpos.pos) generic.check_range(self.ypos.value, 0, 0x7fffFFFF, "Real sprite paramater 'ypos'", self.ypos.pos) generic.check_range(self.xsize.value, 1, 0xFFFF, "Real sprite paramater 'xsize'", self.xsize.pos) generic.check_range(self.ysize.value, 1, 0xFF, "Real sprite paramater 'ysize'", self.ysize.pos) def validate_size(self): """ Check if xpos/ypos/xsize/ysize are already set and if not, set them to 0,0,image_width,image_height. """ if self.xpos is not None: return if not os.path.exists(self.file.value): raise generic.ImageError("File doesn't exist", self.file.value) im = Image.open(self.file.value) self.xpos = expression.ConstantNumeric(0) self.ypos = expression.ConstantNumeric(0) self.xsize = expression.ConstantNumeric(im.size[0]) self.ysize = expression.ConstantNumeric(im.size[1]) self.check_sprite_size() def __str__(self): ret = "" if self.label is not None: ret += str(self.label) + ": " ret += "[" ret += ", ".join([str(param) for param in self.param_list]) ret += "]" return ret class RealSpriteAction(base_action.BaseAction): def __init__(self, sprite): self.sprite = sprite self.last = False self.block_name = None self.label = None self.sprite_num = None def write(self, file): if self.sprite.is_empty: file.print_empty_realsprite() else: file.print_sprite(self.sprite) if self.last: file.newline() class RecolourSprite(object): def __init__(self, mapping): self.mapping = mapping self.label = None def debug_print(self, indentation): print indentation*' ' + 'Recolour sprite, mapping:' for assignment in self.mapping: print (indentation + 2)*' ' + '%s: %s;' % (str(assignment.name), str(assignment.value)) def __str__(self): ret = "recolour_sprite {\n" for assignment in self.mapping: ret += '%s: %s;' % (str(assignment.name), str(assignment.value)) ret += "}" return ret class RecolourSpriteAction(RealSpriteAction): def __init__(self, sprite): RealSpriteAction.__init__(self, sprite) self.output_table = [] def prepare_output(self): colour_mapping = {} for assignment in self.sprite.mapping: if assignment.value.max is not None and assignment.name.max.value - assignment.name.min.value != assignment.value.max.value - assignment.value.min.value: raise generic.ScriptError("From and to ranges in a recolour block need to have the same size", assignment.pos) for i in range(assignment.name.max.value - assignment.name.min.value + 1): idx = assignment.name.min.value + i val = assignment.value.min.value if assignment.value.max is not None: val += i colour_mapping[idx] = val for i in range(256): if i in colour_mapping: colour = colour_mapping[i] else: colour = i self.output_table.append(colour) def write(self, file): file.start_sprite(257) file.print_bytex(0) if file.palette not in ("DOS", "WIN"): raise generic.ScriptError("Recolour sprites are only supported when writing to the DOS or WIN palette. If you don't have any real sprites use the commandline option -p to set a palette.") colour_table = self.output_table if file.palette == "DOS" else convert_palette(self.output_table) for idx, colour in enumerate(colour_table): if idx % 16 == 0: file.newline() file.print_bytex(colour) if self.last: file.newline() file.end_sprite() class TemplateUsage(object): def __init__(self, name, param_list, label, pos): self.name = name self.param_list = param_list self.label = label self.pos = pos def debug_print(self, indentation): print indentation*' ' + 'Template used:', self.name.value print (indentation+2)*' ' + 'Parameters:' for param in self.param_list: param.debug_print(indentation + 4) def get_labels(self): if self.name.value not in sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos) labels, offset = sprite_template_map[self.name.value].get_labels() if self.label is not None: if self.label.value in labels: raise generic.ScriptError("Duplicate label encountered; '%s' already exists." % self.label.value, self.pos) labels[self.label.value] = 0 return labels, offset def expand(self, default_file, parameters): if self.name.value not in sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos) template = sprite_template_map[self.name.value] if len(self.param_list) != len(template.param_list): raise generic.ScriptError("Incorrect number of template arguments. Expected " + str(len(template.param_list)) + ", got " + str(len(self.param_list)), self.pos) param_dict = {} for i, param in enumerate(self.param_list): param = param.reduce([real_sprite_compression_flags, parameters]) if not isinstance(param, (expression.ConstantNumeric, expression.StringLiteral)): raise generic.ScriptError("Template parameters should be compile-time constants", param.pos) param_dict[template.param_list[i].value] = param.value return parse_sprite_list(template.sprite_list, default_file, param_dict, False, None) def __str__(self): return "%s(%s)" % (str(self.name), ", ".join([str(param) for param in self.param_list])) real_sprite_compression_flags = { 'CROP' : 0x00, 'NOCROP' : 0x40, } def parse_real_sprite(sprite, default_file, id_dict): # check the number of parameters num_param = len(sprite.param_list) if num_param == 0: sprite.is_empty = True return RealSpriteAction(sprite) elif not (2 <= num_param <= 9): raise generic.ScriptError("Invalid number of arguments for real sprite. Expected 2..9.", sprite.param_list[0].pos) # create new sprite struct, needed for template expansion new_sprite = RealSprite() param_offset = 0 if num_param >= 6: # xpos, ypos, xsize and ysize are all optional. If not specified they'll default # to 0, 0, image_width, image_height new_sprite.xpos = sprite.param_list[0].reduce_constant([id_dict]) new_sprite.ypos = sprite.param_list[1].reduce_constant([id_dict]) new_sprite.xsize = sprite.param_list[2].reduce_constant([id_dict]) new_sprite.ysize = sprite.param_list[3].reduce_constant([id_dict]) new_sprite.check_sprite_size() param_offset += 4 new_sprite.xrel = sprite.param_list[param_offset].reduce_constant([id_dict]) new_sprite.yrel = sprite.param_list[param_offset + 1].reduce_constant([id_dict]) generic.check_range(new_sprite.xrel.value, -0x8000, 0x7fff, "Real sprite paramater %d 'xrel'" % (param_offset + 1), new_sprite.xrel.pos) generic.check_range(new_sprite.yrel.value, -0x8000, 0x7fff, "Real sprite paramater %d 'yrel'" % (param_offset + 2), new_sprite.yrel.pos) param_offset += 2 new_sprite.compression = expression.ConstantNumeric(0x01) if num_param > param_offset: try: new_sprite.compression = sprite.param_list[param_offset].reduce_constant([real_sprite_compression_flags, id_dict]) param_offset += 1 if (new_sprite.compression.value & ~0x40) != 0: raise generic.ScriptError("Real sprite compression is invalid; can only have the NOCROP bit (0x40) set, encountered " + str(new_sprite.compression.value), new_sprite.compression.pos) new_sprite.compression.value |= 0x01 except generic.ConstError: pass if num_param > param_offset: new_sprite.file = sprite.param_list[param_offset].reduce([id_dict]) param_offset += 1 if not isinstance(new_sprite.file, expression.StringLiteral): raise generic.ScriptError("Real sprite parameter %d 'file' should be a string literal" % (param_offset + 1), new_sprite.file.pos) elif default_file is not None: new_sprite.file = default_file else: raise generic.ScriptError("No image file specified for real sprite", sprite.param_list[0].pos) if num_param > param_offset: new_sprite.mask_file = sprite.param_list[param_offset].reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError("Real sprite parameter %d 'mask_file' should be a string literal" % (param_offset + 1), new_sprite.file.pos) else: new_sprite.mask_file = None return RealSpriteAction(new_sprite) def parse_recolour_sprite(sprite, id_dict): # create new struct, needed for template expansion new_mapping = [] for old_assignment in sprite.mapping: from_min_value = old_assignment.name.min.reduce_constant([id_dict]) from_max_value = from_min_value if old_assignment.name.max is None else old_assignment.name.max.reduce_constant([id_dict]) to_min_value = old_assignment.value.min.reduce_constant([id_dict]) to_max_value = None if old_assignment.value.max is None else old_assignment.value.max.reduce_constant([id_dict]) new_mapping.append(assignment.Assignment(assignment.Range(from_min_value, from_max_value), assignment.Range(to_min_value, to_max_value), old_assignment.pos)) new_sprite = RecolourSprite(new_mapping) return RecolourSpriteAction(new_sprite) sprite_template_map = {} def parse_sprite_list(sprite_list, default_file, parameters = {}, outer_scope = True, block_name = None): assert block_name is None or isinstance(block_name, expression.Identifier) real_sprite_list = [] for sprite in sprite_list: if isinstance(sprite, RealSprite): new_sprites = [parse_real_sprite(sprite, default_file, parameters)] elif isinstance(sprite, RecolourSprite): new_sprites = [parse_recolour_sprite(sprite, parameters)] else: new_sprites = sprite.expand(default_file, parameters) if outer_scope and sprite.label is not None: new_sprites[0].label = sprite.label real_sprite_list.extend(new_sprites) if outer_scope: real_sprite_list[-1].last = True if block_name: real_sprite_list[0].block_name = block_name.value return real_sprite_list nml-0.2.4/nml/actions/action0properties.py0000644000061700006170000010776612036626442021327 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.expression import ConstantNumeric, ConstantFloat, Array, StringLiteral, Identifier tilelayout_names = {} class Action0Property(object): """ @ivar num: Number of the property. @type num: C{int} @ivar values: Value of the property for each id. @type values: C{list} of L{ConstantNumeric} @ivar size: Size of the storage, in bytes. @type size: C{int} """ def __init__(self, num, value, size): self.num = num self.values = value if isinstance(value, list) else [value] self.size = size # Make sure the value fits in the size. # Strings have their own check in parse_property for val in self.values: if not isinstance(val, StringLiteral): biggest = 1 << (8 * size) if val.value >= biggest: raise generic.ScriptError("Action 0 property too large", val.pos) elif val.value < 0 and val.value + (biggest / 2) < 0: raise generic.ScriptError("Action 0 property too small", val.pos) def write(self, file): file.print_bytex(self.num) for val in self.values: val.write(file, self.size) file.newline() def get_size(self): return self.size * len(self.values) + 1 # @var properties: A mapping of features to properties. This is a list # with one item per feature. Entries should be a dictionary of properties, # or C{None} if no properties are defined for that feature. # # Each property is a mapping of property name to its characteristics. # First a short summary is given, then the recognized characteristics # are outlined below in more detail. # # Summary: If 'string' or 'string_literal' is set, the value should be a # string or literal string, else the value is a number. 'unit_type' and # 'unit_conversion' are used to convert user-entered values to nfo values. # 'custom_function' can be used to create a special mapping of the value to nfo # properties, else 'num' and 'size' are used to provide a 'normal' action0. # 'append_function' can be used to append one or more other properties. # # 'string', if set, means that the value of the property should be a string. # The value of characteristic indicates the string range to use (usually 0xD0 or 0xDC) # If set to None, the string will use the ID of the item (used for vehicle names) # # 'string_literal', if set, indicates that the value of the property should # be a literal (quoted) string. The value of the characteristic is equal to # the required length (usually 4) of said literal string. # # 'unit_type' means that units of the given type (power, speed) can be applied # to this property. A list of units can be found in ../unit.py. The value is then # converted to a certain reference unit (for example m/s for speed) # Leaving this unset means that no units (except 'nfo' which is an identity mapping) # can be applied to this property. # # 'unit_conversion' defines a conversion factor between the value entered by # the user and the resulting value in nfo. The entered value (possibly converted # to the appropriate reference unit, see 'unit_type' above) is multiplied by # this factor and then rounded to an integer to provide the final value. # This parameter is not required and defaults to 1. # # 'custom_function' can be used to bypass the normal way of converting the # name / value to an Action0Property. This function is called with one argument, # which is the value of the property. It should return a list of Action0Property. # To pass extra parameters to the function, a dose of lambda calculus can be used. # Consult the code for examples. # # 'append_function' works similarly to 'custum_function', but instead of # replacing the normal property generation it merely adds to it. The parameter # and return value are the same, but now the 'normal' action0 property is # generated as well. # # 'num' is the Action0 property number of the action 0 property, as given by the # nfo specs. If set to -1, no action0 property will be generated. If # 'custom_function' is set, this value is not needed and can be left out. # # 'size' is the size (in bytes) of the resulting action 0 property. Valid # values are 1 (byte), 2 (word) or 4 (dword). For other (or variable) sizes, # 'custom_function' is needed. If 'custom_function' is set or 'num' is equal # to -1, this parameter is not needed and can be left out. # properties = 0x12 * [None] # # Some helper functions that are used for multiple features # def two_byte_property(value, low_prop, high_prop): """ Decode a two byte value into two action 0 properties. @param value: Value to encode. @type value: L{ConstantNumeric} @param low_prop: Property number for the low 8 bits of the value. @type low_prop: C{int} @param high_prop: Property number for the high 8 bits of the value. @type high_prop: C{int} @return: Sequence of two action 0 properties (low part, high part). @rtype: C{list} of L{Action0Property} """ value = value.reduce_constant() low_byte = ConstantNumeric(value.value & 0xFF) high_byte = ConstantNumeric(value.value >> 8) return [Action0Property(low_prop, low_byte, 1), Action0Property(high_prop, high_byte, 1)] def append_cargo_type(feature): propnums = [0x15, 0x10, 0x0C] return lambda value: [Action0Property(propnums[feature], ConstantNumeric(0xFF), 1)] def animation_info(prop_num, value, loop_bit=8, max_frame=253, prop_size=2): """ Convert animation info array of two elements to an animation info property. The first is 0/1, and defines whether or not the animation loops. The second is the number of frames, at most 253 frames. @param prop_num: Property number. @type prop_num: C{int} @param value: Array of animation info. @type value: C{Array} @param loop_bit: Bit the loop information is stored. @type loop_bit: C{int} @param max_frame: Max frames possible. @type max_frame: C{int} @param prop_size: Property size in bytes. @type prop_size: C{int} @return: Animation property. @rtype: C{list} of L{Action0Property} """ if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("animation_info must be an array with exactly 2 constant values", value.pos) looping = value.values[0].reduce_constant().value frames = value.values[1].reduce_constant().value if looping not in (0, 1): raise generic.ScriptError("First field of the animation_info array must be either 0 or 1", value.values[0].pos) if frames < 1 or frames > max_frame: raise generic.ScriptError("Second field of the animation_info array must be between 1 and " + str(max_frame), value.values[1].pos) return [Action0Property(prop_num, ConstantNumeric((looping << loop_bit) + frames - 1), prop_size)] def cargo_list(value, max_num_cargos, prop_num, prop_size): """ Encode an array of cargo types in a single property. If less than the maximum number of cargos are given the rest is filled up with 0xFF (=invalid cargo). @param value: Array of cargo types. @type value: C{Array} @param max_num_cargos: The maximum number of cargos in the array. @type max_num_cargos: C{int} @param prop_num: Property number. @type prop_num: C{int} @param prop_size: Property size in bytes. @type prop_size: C{int} """ if not isinstance(value, Array) or len(value.values) > max_num_cargos: raise generic.ScriptError("Cargo list must be an array with no more than %d values" % max_num_cargos, value.pos) cargoes = [val.reduce_constant().value for val in value.values] + [0xFF for _ in range(prop_size)] val = 0 for i in range(prop_size): val = val | (cargoes[i] << (i * 8)) return [Action0Property(prop_num, ConstantNumeric(val), prop_size)] # # General vehicle properties that apply to feature 0x00 .. 0x03 # general_veh_props = { 'reliability_decay' : {'size': 1, 'num': 0x02}, 'vehicle_life' : {'size': 1, 'num': 0x03}, 'model_life' : {'size': 1, 'num': 0x04}, 'climates_available' : {'size': 1, 'num': 0x06}, 'loading_speed' : {'size': 1, 'num': 0x07}, 'name' : {'num': -1, 'string': None}, } def ottd_display_speed(value, divisor, unit): return (value.value / divisor * 10 / 16 * unit.ottd_mul) >> unit.ottd_shift # # Feature 0x00 (Trains) # properties[0x00] = { 'track_type' : {'size': 1, 'num': 0x05}, 'ai_special_flag' : {'size': 1, 'num': 0x08}, 'speed' : {'size': 2, 'num': 0x09, 'unit_type': 'speed', 'unit_conversion': 3.5790976, 'adjust_value': lambda val, unit: ottd_display_speed(val, 1, unit)}, 'power' : {'size': 2, 'num': 0x0B, 'unit_type': 'power'}, 'running_cost_factor' : {'size': 1, 'num': 0x0D}, 'running_cost_base' : {'size': 4, 'num': 0x0E}, 'sprite_id' : {'size': 1, 'num': 0x12}, 'dual_headed' : {'size': 1, 'num': 0x13}, 'cargo_capacity' : {'size': 1, 'num': 0x14}, 'weight' : {'custom_function': lambda x: two_byte_property(x, 0x16, 0x24), 'unit_type': 'weight'}, 'cost_factor' : {'size': 1, 'num': 0x17}, 'ai_engine_rank' : {'size': 1, 'num': 0x18}, 'engine_class' : {'size': 1, 'num': 0x19}, 'extra_power_per_wagon' : {'size': 2, 'num': 0x1B, 'unit_type': 'power'}, 'refit_cost' : {'size': 1, 'num': 0x1C}, 'refittable_cargo_types' : {'size': 4, 'num': 0x1D, 'append_function': append_cargo_type(0x00)}, 'callback_flags' : {'size': 1, 'num': 0x1E}, 'tractive_effort_coefficient' : {'size': 1, 'num': 0x1F, 'unit_conversion': 255}, 'air_drag_coefficient' : {'size': 1, 'num': 0x20, 'unit_conversion': 255}, 'shorten_vehicle' : {'size': 1, 'num': 0x21}, 'visual_effect_and_powered' : {'size': 1, 'num': 0x22}, 'extra_weight_per_wagon' : {'size': 1, 'num': 0x23, 'unit_type': 'weight'}, 'bitmask_vehicle_info' : {'size': 1, 'num': 0x25}, 'retire_early' : {'size': 1, 'num': 0x26}, 'misc_flags' : {'size': 1, 'num': 0x27}, 'refittable_cargo_classes' : {'size': 2, 'num': 0x28, 'append_function': append_cargo_type(0x00)}, 'non_refittable_cargo_classes' : {'size': 2, 'num': 0x29, 'append_function': append_cargo_type(0x00)}, 'introduction_date' : {'size': 4, 'num': 0x2A}, 'cargo_age_period' : {'size': 2, 'num': 0x2B}, } properties[0x00].update(general_veh_props) # # Feature 0x01 (Road Vehicles) # def roadveh_speed_prop(value): value = value.reduce_constant() prop08 = ConstantNumeric(min(value.value, 0xFF)) props = [Action0Property(0x08, prop08, 1)] if value.value > 0xFF: prop15 = ConstantNumeric((value.value + 3) / 4) props.append(Action0Property(0x15, prop15, 1)) return props properties[0x01] = { 'speed' : {'custom_function' : roadveh_speed_prop, 'unit_type': 'speed', 'unit_conversion': 7.1581952, 'adjust_value': lambda val, unit: ottd_display_speed(val, 2, unit)}, 'running_cost_factor' : {'size': 1, 'num': 0x09}, 'running_cost_base' : {'size': 4, 'num': 0x0A}, 'sprite_id' : {'size': 1, 'num': 0x0E}, 'cargo_capacity' : {'size': 1, 'num': 0x0F}, 'cost_factor' : {'size': 1, 'num': 0x11}, 'sound_effect' : {'size': 1, 'num': 0x12}, 'power' : {'size': 1, 'num': 0x13, 'unit_type': 'power', 'unit_conversion': 0.1}, 'weight' : {'size': 1, 'num': 0x14, 'unit_type': 'weight', 'unit_conversion': 4}, 'refittable_cargo_types' : {'size': 4, 'num': 0x16, 'append_function': append_cargo_type(0x01)}, 'callback_flags' : {'size': 1, 'num': 0x17}, 'tractive_effort_coefficient' : {'size': 1, 'num': 0x18, 'unit_conversion': 255}, 'air_drag_coefficient' : {'size': 1, 'num': 0x19, 'unit_conversion': 255}, 'refit_cost' : {'size': 1, 'num': 0x1A}, 'retire_early' : {'size': 1, 'num': 0x1B}, 'misc_flags' : {'size': 1, 'num': 0x1C}, 'refittable_cargo_classes' : {'size': 2, 'num': 0x1D, 'append_function': append_cargo_type(0x01)}, 'non_refittable_cargo_classes' : {'size': 2, 'num': 0x1E, 'append_function': append_cargo_type(0x01)}, 'introduction_date' : {'size': 4, 'num': 0x1F}, 'visual_effect' : {'size': 1, 'num': 0x21}, 'cargo_age_period' : {'size': 2, 'num': 0x22}, } properties[0x01].update(general_veh_props) # # Feature 0x02 (Ships) # def speed_fraction_prop(value, propnr): # Unit is already converted to 0 .. 255 range when we get here value = value.reduce_constant() if not (0 <= value.value <= 255): # Do not use check_range to provide better error message raise generic.ScriptError("speed fraction must be in range 0 .. 1", value.pos) value = ConstantNumeric(255 - value.value, value.pos) return [Action0Property(propnr, value, 1)] properties[0x02] = { 'sprite_id' : {'size': 1, 'num': 0x08}, 'is_refittable' : {'size': 1, 'num': 0x09}, 'cost_factor' : {'size': 1, 'num': 0x0A}, 'speed' : {'size': 1, 'num': 0x0B, 'unit_type': 'speed', 'unit_conversion': 7.1581952, 'adjust_value': lambda val, unit: ottd_display_speed(val, 2, unit)}, 'cargo_capacity' : {'size': 2, 'num': 0x0D}, 'running_cost_factor' : {'size': 1, 'num': 0x0F}, 'sound_effect' : {'size': 1, 'num': 0x10}, 'refittable_cargo_types' : {'size': 4, 'num': 0x11, 'append_function': append_cargo_type(0x02)}, 'callback_flags' : {'size': 1, 'num': 0x12}, 'refit_cost' : {'size': 1, 'num': 0x13}, 'ocean_speed_fraction' : {'size': 1, 'num': 0x14, 'unit_conversion': 255, 'custom_function': lambda val: speed_fraction_prop(val, 0x14)}, 'canal_speed_fraction' : {'size': 1, 'num': 0x15, 'unit_conversion': 255, 'custom_function': lambda val: speed_fraction_prop(val, 0x15)}, 'retire_early' : {'size': 1, 'num': 0x16}, 'misc_flags' : {'size': 1, 'num': 0x17}, 'refittable_cargo_classes' : {'size': 2, 'num': 0x18, 'append_function': append_cargo_type(0x02)}, 'non_refittable_cargo_classes' : {'size': 2, 'num': 0x19, 'append_function': append_cargo_type(0x02)}, 'introduction_date' : {'size': 4, 'num': 0x1A}, 'visual_effect' : {'size': 1, 'num': 0x1C}, 'cargo_age_period' : {'size': 2, 'num': 0x1D}, } properties[0x02].update(general_veh_props) # # Feature 0x03 (Aircraft) # properties[0x03] = { 'sprite_id' : {'size': 1, 'num': 0x08}, 'is_helicopter' : {'size': 1, 'num': 0x09}, 'is_large' : {'size': 1, 'num': 0x0A}, 'cost_factor' : {'size': 1, 'num': 0x0B}, 'speed' : {'size': 1, 'num': 0x0C, 'unit_type': 'speed', 'unit_conversion': 0.279617, 'adjust_value': lambda val, unit: ottd_display_speed(val, 1, unit)}, 'acceleration' : {'size': 1, 'num': 0x0D}, 'running_cost_factor' : {'size': 1, 'num': 0x0E}, 'passenger_capacity' : {'size': 2, 'num': 0x0F}, 'mail_capacity' : {'size': 1, 'num': 0x11}, 'sound_effect' : {'size': 1, 'num': 0x12}, 'refittable_cargo_types' : {'size': 4, 'num': 0x13}, 'callback_flags' : {'size': 1, 'num': 0x14}, 'refit_cost' : {'size': 1, 'num': 0x15}, 'retire_early' : {'size': 1, 'num': 0x16}, 'misc_flags' : {'size': 1, 'num': 0x17}, 'refittable_cargo_classes' : {'size': 2, 'num': 0x18}, 'non_refittable_cargo_classes' : {'size': 2, 'num': 0x19}, 'introduction_date' : {'size': 4, 'num': 0x1A}, 'cargo_age_period' : {'size': 2, 'num': 0x1C}, 'range' : {'size': 2, 'num': 0x1F}, } properties[0x03].update(general_veh_props) # TODO: Feature 0x04 .. 0x06 (Stations, Canals, Bridges) properties[0x05] = { 'callback_flags' : {'size': 1, 'num': 0x08}, 'graphic_flags' : {'size': 1, 'num': 0x09}, } # # Feature 0x07 (Houses) # def house_available_years(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = value.values[0].reduce_constant().value max_year = value.values[1].reduce_constant().value min_year_safe = min(max(min_year - 1920, 0), 255) max_year_safe = min(max(max_year - 1920, 0), 255) return [Action0Property(0x0A, ConstantNumeric(max_year_safe << 8 | min_year_safe), 2), Action0Property(0x21, ConstantNumeric(min_year), 2), Action0Property(0x22, ConstantNumeric(max_year), 2)] def house_random_colours(value): if not isinstance(value, Array) or len(value.values) != 4: raise generic.ScriptError("Random colours must be an array with exactly four values", value.pos) colours = [val.reduce_constant().value for val in value.values] for colour in colours: if colour < 0 or colour > 15: raise generic.ScriptError("Random house colours must be a value between 0 and 15", value.pos) return [Action0Property(0x17, ConstantNumeric(colours[0] << 24 | colours[1] << 16 | colours[2] << 8 | colours[3]), 4)] def house_available_mask(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("availability_mask must be an array with exactly 2 values", value.pos) town_zones = value.values[0].reduce_constant().value climates = value.values[1].reduce_constant().value return [Action0Property(0x13, ConstantNumeric(town_zones | (climates & 0x800) | ((climates & 0x0F) << 12)), 2)] properties[0x07] = { 'substitute' : {'size': 1, 'num': 0x08}, 'building_flags' : {'custom_function': lambda x: two_byte_property(x, 0x09, 0x19)}, 'years_available' : {'custom_function': house_available_years}, 'population' : {'size': 1, 'num': 0x0B}, 'mail_multiplier' : {'size': 1, 'num': 0x0C}, 'pax_acceptance' : {'size': 1, 'num': 0x0D, 'unit_conversion': 8}, 'mail_acceptance' : {'size': 1, 'num': 0x0E, 'unit_conversion': 8}, 'cargo_acceptance' : {'size': 1, 'num': 0x0F, 'unit_conversion': 8}, 'local_authority_impact' : {'size': 2, 'num': 0x10}, 'removal_cost_multiplier' : {'size': 1, 'num': 0x11}, 'name' : {'size': 2, 'num': 0x12, 'string': 0xDC}, 'availability_mask' : {'custom_function': house_available_mask}, 'callback_flags' : {'custom_function': lambda x: two_byte_property(x, 0x14, 0x1D)}, 'override' : {'size': 1, 'num': 0x15}, 'refresh_multiplier' : {'size': 1, 'num': 0x16}, 'random_colours' : {'custom_function': house_random_colours}, 'probability' : {'size': 1, 'num': 0x18, 'unit_conversion': 16}, 'animation_info' : {'custom_function': lambda value: animation_info(0x1A, value, 7, 128, 1)}, 'animation_speed' : {'size': 1, 'num': 0x1B}, 'building_class' : {'size': 1, 'num': 0x1C}, 'accepted_cargos' : {'custom_function': lambda value: cargo_list(value, 3, 0x1E, 4)}, 'minimum_lifetime' : {'size': 1, 'num': 0x1F}, } # Feature 0x08 (General Vars) is implemented elsewhere (e.g. basecost, snowline) # # Feature 0x09 (Industry Tiles) # def industrytile_cargos(value): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) prop_num = 0x0A props = [] for cargo_amount_pair in value.values: if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) cargo_id = cargo_amount_pair.values[0].reduce_constant().value cargo_amount = cargo_amount_pair.values[1].reduce_constant().value props.append(Action0Property(prop_num, ConstantNumeric((cargo_amount << 8) | cargo_id), 2)) prop_num += 1 while prop_num <= 0x0C: props.append(Action0Property(prop_num, ConstantNumeric(0), 2)) prop_num += 1 return props properties[0x09] = { 'substitute' : {'size': 1, 'num': 0x08}, 'override' : {'size': 1, 'num': 0x09}, 'accepted_cargos' : {'custom_function': industrytile_cargos}, 'land_shape_flags' : {'size': 1, 'num': 0x0D}, 'callback_flags' : {'size': 1, 'num': 0x0E}, 'animation_info' : {'custom_function': lambda value: animation_info(0x0F, value)}, 'animation_speed' : {'size': 1, 'num': 0x10}, 'animation_triggers' : {'size': 1, 'num': 0x11}, 'special_flags' : {'size': 1, 'num': 0x12}, } # # Feature 0x0A (Industries) # class IndustryLayoutProp(object): def __init__(self, layout_list): self.layout_list = layout_list def write(self, file): file.print_bytex(0x0A) file.print_byte(len(self.layout_list)) # -6 because prop_num, num_layouts and size should not be included file.print_dword(self.get_size() - 6) file.newline() for layout in self.layout_list: layout.write(file) file.newline() def get_size(self): size = 6 for layout in self.layout_list: size += layout.get_size() return size def industry_layouts(value): if not isinstance(value, Array) or not all(map(lambda x: isinstance(x, Identifier), value.values)): raise generic.ScriptError("layouts must be an array of layout names", value.pos) layouts = [] for name in value.values: if name.value not in tilelayout_names: raise generic.ScriptError("Unknown layout name '%s'" % name.value, name.pos) layouts.append(tilelayout_names[name.value]) return [IndustryLayoutProp(layouts)] def industry_prod_multiplier(value): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Prod multiplier must be an array of up to two values", value.pos) props = [] for i in range(0, 2): val = value.values[i].reduce_constant() if i < len(value.values) else ConstantNumeric(0) props.append(Action0Property(0x12 + i, val, 1)) return props class RandomSoundsProp(object): def __init__(self, sound_list): self.sound_list = sound_list def write(self, file): file.print_bytex(0x15) file.print_byte(len(self.sound_list)) for sound in self.sound_list: sound.write(file, 1) file.newline() def get_size(self): return len(self.sound_list) + 2 def random_sounds(value): if not isinstance(value, Array) or not all(map(lambda x: isinstance(x, ConstantNumeric), value.values)): raise generic.ScriptError("random_sound_effects must be an array with sounds effects", value.pos) return [RandomSoundsProp(value.values)] class ConflictingTypesProp(object): def __init__(self, types_list): self.types_list = types_list assert len(self.types_list) == 3 def write(self, file): file.print_bytex(0x16) for type in self.types_list: type.write(file, 1) file.newline() def get_size(self): return len(self.types_list) + 1 def industry_conflicting_types(value): if not isinstance(value, Array): raise generic.ScriptError("conflicting_ind_types must be an array of industry types", value.pos) if len(value.values) > 3: raise generic.ScriptError("conflicting_ind_types may have at most three entries", value.pos) types_list = [] for val in value.values: types_list.append(val.reduce_constant()) while len(types_list) < 3: types_list.append(ConstantNumeric(0xFF)) return [ConflictingTypesProp(types_list)] def industry_input_multiplier(value, prop_num): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Input multiplier must be an array of up to two values", value.pos) val1 = value.values[0].reduce() if len(value.values) > 0 else ConstantNumeric(0) val2 = value.values[1].reduce() if len(value.values) > 1 else ConstantNumeric(0) if not isinstance(val1, (ConstantNumeric, ConstantFloat)) or not isinstance(val2, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Expected a compile-time constant", value.pos) generic.check_range(val1.value, 0, 256, "input_multiplier", val1.pos) generic.check_range(val2.value, 0, 256, "input_multiplier", val2.pos) mul1 = int(val1.value * 256) mul2 = int(val2.value * 256) return [Action0Property(prop_num, ConstantNumeric(mul1 | (mul2 << 16)), 4)] properties[0x0A] = { 'substitute' : {'size': 1, 'num': 0x08}, 'override' : {'size': 1, 'num': 0x09}, 'layouts' : {'custom_function': industry_layouts}, 'life_type' : {'size': 1, 'num': 0x0B}, 'closure_msg' : {'size': 2, 'num': 0x0C}, 'prod_increase_msg' : {'size': 2, 'num': 0x0D}, 'prod_decrease_msg' : {'size': 2, 'num': 0x0E}, 'fund_cost_multiplier' : {'size': 1, 'num': 0x0F}, 'prod_cargo_types' : {'custom_function': lambda value: cargo_list(value, 2, 0x10, 2)}, 'accept_cargo_types' : {'custom_function': lambda value: cargo_list(value, 3, 0x11, 4)}, 'prod_multiplier' : {'custom_function': industry_prod_multiplier}, 'min_cargo_distr' : {'size': 1, 'num': 0x14}, 'random_sound_effects' : {'custom_function': random_sounds}, 'conflicting_ind_types' : {'custom_function': industry_conflicting_types}, 'prob_random' : {'size': 1, 'num': 0x17}, 'prob_in_game' : {'size': 1, 'num': 0x18}, 'map_colour' : {'size': 1, 'num': 0x19}, 'spec_flags' : {'size': 4, 'num': 0x1A}, 'new_ind_msg' : {'size': 2, 'num': 0x1B}, 'input_multiplier_1' : {'custom_function': lambda value: industry_input_multiplier(value, 0x1C)}, 'input_multiplier_2' : {'custom_function': lambda value: industry_input_multiplier(value, 0x1D)}, 'input_multiplier_3' : {'custom_function': lambda value: industry_input_multiplier(value, 0x1E)}, 'name' : {'size': 2, 'num': 0x1F, 'string': 0xDC}, 'prospect_chance' : {'size': 4, 'num': 0x20, 'unit_conversion': 0xFFFFFFFF}, 'callback_flags' : {'custom_function': lambda x: two_byte_property(x, 0x21, 0x22)}, 'remove_cost_multiplier' : {'size': 4, 'num': 0x23}, 'nearby_station_name' : {'size': 2, 'num': 0x24, 'string': 0xDC}, } # # Feature 0x0B (Cargos) # properties[0x0B] = { 'number' : {'num' : 0x08, 'size' : 1}, 'type_name' : {'num' : 0x09, 'size' : 2, 'string' : 0xDC}, 'unit_name' : {'num' : 0x0A, 'size' : 2, 'string' : 0xDC}, 'single_unit_text' : {'num' : 0x0B, 'size' : 2, 'string' : 0xDC}, 'multiple_units_text' : {'num' : 0x0C, 'size' : 2, 'string' : 0xDC}, 'type_abbreviation' : {'num' : 0x0D, 'size' : 2, 'string' : 0xDC}, 'sprite' : {'num' : 0x0E, 'size' : 2}, 'weight' : {'num' : 0x0F, 'size' : 1, 'unit_type' : 'weight', 'unit_conversion' : 16}, 'penalty_lowerbound' : {'num' : 0x10, 'size' : 1}, 'single_penalty_length' : {'num' : 0x11, 'size' : 1}, 'price_factor' : {'num' : 0x12, 'size' : 4, 'unit_conversion' : (1 << 21) / (10.0 * 20 * 255)}, # 10 units of cargo across 20 tiles, with time factor = 255 'station_list_colour' : {'num' : 0x13, 'size' : 1}, 'cargo_payment_list_colour' : {'num' : 0x14, 'size' : 1}, 'is_freight' : {'num' : 0x15, 'size' : 1}, 'cargo_classes' : {'num' : 0x16, 'size' : 2}, 'cargo_label' : {'num' : 0x17, 'size' : 4, 'string_literal': 4}, 'town_growth_effect' : {'num' : 0x18, 'size' : 1}, 'town_growth_multiplier' : {'num' : 0x19, 'size' : 2, 'unit_conversion' : 0x100}, 'callback_flags' : {'num' : 0x1A, 'size' : 1}, 'units_of_cargo' : {'num' : 0x1B, 'size' : 2, 'string' : 0xDC}, 'items_of_cargo' : {'num' : 0x1C, 'size' : 2, 'string' : 0xDC}, } # TODO: Feature 0x0C (Sound Effects) # # Feature 0x0D (Airports) # def airport_years(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = value.values[0].reduce_constant() max_year = value.values[1].reduce_constant() return [Action0Property(0x0C, ConstantNumeric(max_year.value << 16 | min_year.value), 4)] class AirportLayoutProp(object): def __init__(self, layout_list): self.layout_list = layout_list def write(self, file): file.print_bytex(0x0A) file.print_byte(len(self.layout_list)) # -6 because prop_num, num_layouts and size should not be included file.print_dword(self.get_size() - 6) file.newline() for layout in self.layout_list: file.print_bytex(layout.properties['rotation'].value) layout.write(file) file.newline() def get_size(self): size = 6 for layout in self.layout_list: size += layout.get_size() + 1 return size def airport_layouts(value): if not isinstance(value, Array) or not all(map(lambda x: isinstance(x, Identifier), value.values)): raise generic.ScriptError("layouts must be an array of layout names", value.pos) layouts = [] for name in value.values: if name.value not in tilelayout_names: raise generic.ScriptError("Unknown layout name '%s'" % name.value, name.pos) layout = tilelayout_names[name.value] if 'rotation' not in layout.properties: raise generic.ScriptError("Airport layouts must have the 'rotation' property", layout.pos) if layout.properties['rotation'].value not in (0, 2, 4, 6): raise generic.ScriptError("Airport layout rotation is not a valid direction.", layout.properties['rotation'].pos) layouts.append(layout) return [AirportLayoutProp(layouts)] properties[0x0D] = { 'override' : {'size': 1, 'num': 0x08}, 'layouts' : {'custom_function': airport_layouts}, 'years_available' : {'custom_function': airport_years}, 'ttd_airport_type' : {'size': 1, 'num': 0x0D}, 'catchment_area' : {'size': 1, 'num': 0x0E}, 'noise_level' : {'size': 1, 'num': 0x0F}, 'name' : {'size': 2, 'num': 0x10, 'string': 0xDC}, } # Feature 0x0E (Signals) doesn't currently have any action0 # # Feature 0x0F (Objects) # def object_size(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Object size must be an array with exactly two values", value.pos) sizex = value.values[0].reduce_constant() sizey = value.values[1].reduce_constant() if sizex.value < 1 or sizex.value > 15 or sizey.value < 1 or sizey.value > 15: raise generic.ScriptError("The size of an object must be at least 1x1 and at most 15x15 tiles", value.pos) return [Action0Property(0x0C, ConstantNumeric(sizey.value << 4 | sizex.value), 1)] properties[0x0F] = { 'class' : {'size': 4, 'num': 0x08, 'string_literal': 4}, # strings might be according to specs be either 0xD0 or 0xD4 'classname' : {'size': 2, 'num': 0x09, 'string': 0xD0}, 'name' : {'size': 2, 'num': 0x0A, 'string': 0xD0}, 'climates_available' : {'size': 1, 'num': 0x0B}, 'size' : {'custom_function': object_size}, 'build_cost_multiplier' : {'size': 1, 'num': 0x0D}, 'introduction_date' : {'size': 4, 'num': 0x0E}, 'end_of_life_date' : {'size': 4, 'num': 0x0F}, 'object_flags' : {'size': 2, 'num': 0x10}, 'animation_info' : {'custom_function': lambda value: animation_info(0x11, value)}, 'animation_speed' : {'size': 1, 'num': 0x12}, 'animation_triggers' : {'size': 2, 'num': 0x13}, 'remove_cost_multiplier' : {'size': 1, 'num': 0x14}, 'callback_flags' : {'size': 2, 'num': 0x15}, 'height' : {'size': 1, 'num': 0x16}, 'num_views' : {'size': 1, 'num': 0x17}, } # # Feature 0x10 (Rail Types) # class RailtypeListProp(object): def __init__(self, prop_num, railtype_list): self.prop_num = prop_num self.railtype_list = railtype_list def write(self, file): file.print_bytex(self.prop_num) file.print_byte(len(self.railtype_list)) for railtype in self.railtype_list: railtype.write(file, 4) file.newline() def get_size(self): return len(self.railtype_list) * 4 + 2 def railtype_list(value, prop_num): if not isinstance(value, Array): raise generic.ScriptError("Railtype list must be an array of literal strings", value.pos) for val in value.values: if not isinstance(val, StringLiteral): raise generic.ScriptError("Railtype list must be an array of literal strings", val.pos) return [RailtypeListProp(prop_num, value.values)] properties[0x10] = { 'label' : {'size': 4, 'num': 0x08, 'string_literal': 4}, 'name' : {'size': 2, 'num': 0x09, 'string': 0xDC}, 'menu_text' : {'size': 2, 'num': 0x0A, 'string': 0xDC}, 'build_window_caption' : {'size': 2, 'num': 0x0B, 'string': 0xDC}, 'autoreplace_text' : {'size': 2, 'num': 0x0C, 'string': 0xDC}, 'new_engine_text' : {'size': 2, 'num': 0x0D, 'string': 0xDC}, 'compatible_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x0E)}, 'powered_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x0F)}, 'railtype_flags' : {'size': 1, 'num': 0x10}, 'curve_speed_multiplier' : {'size': 1, 'num': 0x11}, 'station_graphics' : {'size': 1, 'num': 0x12}, 'construction_cost' : {'size': 2, 'num': 0x13}, 'speed_limit' : {'size': 2, 'num': 0x14, 'unit_type': 'speed', 'unit_conversion': 3.5790976}, 'acceleration_model' : {'size': 1, 'num': 0x15}, 'map_colour' : {'size': 1, 'num': 0x16}, 'introduction_date' : {'size': 4, 'num': 0x17}, 'requires_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x18)}, 'introduces_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x19)}, 'sort_order' : {'size': 1, 'num': 0x1A}, } # # Feature 0x11 (Airport Tiles) # properties[0x11] = { 'substitute' : {'size': 1, 'num': 0x08}, 'override' : {'size': 1, 'num': 0x09}, 'callback_flags' : {'size': 1, 'num': 0x0E}, 'animation_info' : {'custom_function': lambda value: animation_info(0x0F, value)}, 'animation_speed' : {'size': 1, 'num': 0x10}, 'animation_triggers' : {'size': 1, 'num': 0x11}, } nml-0.2.4/nml/actions/sprite_count.py0000644000061700006170000000167112036626442020357 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class SpriteCountAction(object): def __init__(self, count): self.count = count def prepare_output(self): pass def write(self, file): file.start_sprite(4) file.print_dword(self.count) file.newline() file.end_sprite() nml-0.2.4/nml/actions/action2real.py0000644000061700006170000001123112036626442020035 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import action2, action1 from nml.ast import general class Action2Real(action2.Action2): def __init__(self, feature, name, loaded_list, loading_list): action2.Action2.__init__(self, feature, name) self.loaded_list = loaded_list self.loading_list = loading_list def write(self, file): size = 2 + 2 * len(self.loaded_list) + 2 * len(self.loading_list) action2.Action2.write_sprite_start(self, file, size) file.print_byte(len(self.loaded_list)) file.print_byte(len(self.loading_list)) file.newline() for i in self.loaded_list: file.print_word(i) file.newline() for i in self.loading_list: file.print_word(i) file.newline() file.end_sprite() def get_real_action2s(spritegroup, feature): loaded_list = [] loading_list = [] actions = [] if feature not in action2.features_sprite_group: raise generic.ScriptError("Sprite groups that combine sprite sets are not supported for feature '%s'." % general.feature_name(feature), spritegroup.pos) # First make sure that all referenced real sprites are put in a single action1 spriteset_list = [] for view in spritegroup.spriteview_list: spriteset_list.extend([action2.resolve_spritegroup(sg_ref.name) for sg_ref in view.spriteset_list]) actions.extend(action1.add_to_action1(spriteset_list, feature, spritegroup.pos)) view_names = sorted([view.name.value for view in spritegroup.spriteview_list]) if feature in (0x00, 0x01, 0x02, 0x03): if view_names != sorted(['loading', 'loaded']): raise generic.ScriptError("Expected a 'loading' and a 'loaded' (list of) sprite set(s).", spritegroup.pos) elif feature in (0x05, 0x0B, 0x0D, 0x10): generic.print_warning("Sprite groups for feature %02X will not be supported in the future, as they are no longer needed. Directly refer to sprite sets instead." % feature, spritegroup.pos) if view_names != ['default']: raise generic.ScriptError("Expected only a 'default' (list of) sprite set(s).", spritegroup.pos) for view in spritegroup.spriteview_list: if len(view.spriteset_list) == 0: raise generic.ScriptError("Expected at least one sprite set, encountered 0.", view.pos) for set_ref in view.spriteset_list: spriteset = action2.resolve_spritegroup(set_ref.name) action1_index = action1.get_action1_index(spriteset) if view.name.value == 'loading': loading_list.append(action1_index) else: loaded_list.append(action1_index) actions.append(Action2Real(feature, spritegroup.name.value + (" - feature %02X" % feature), loaded_list, loading_list)) spritegroup.set_action2(actions[-1], feature) return actions def create_spriteset_actions(spritegroup): """ Create action2s for directly-referenced sprite sets @param spritegroup: Spritegroup to create the sprite sets for @type spritegroup: L{ASTSpriteGroup} @return: Resulting list of actions @rtype: C{list} of L{BaseAction} """ action_list = [] # Iterate over features first for more efficient action1s for feature in spritegroup.feature_set: if len(spritegroup.used_sprite_sets) != 0 and feature not in action2.features_sprite_group: raise generic.ScriptError("Directly referring to sprite sets is not possible for feature %02X" % feature, spritegroup.pos) for spriteset in spritegroup.used_sprite_sets: if spriteset.has_action2(feature): continue action_list.extend(action1.add_to_action1([spriteset], feature, spritegroup.pos)) action1_index = action1.get_action1_index(spriteset) real_action2 = Action2Real(feature, spriteset.name.value + (" - feature %02X" % feature), [action1_index], [action1_index] if feature <= 0x03 else []) action_list.append(real_action2) spriteset.set_action2(real_action2, feature) return action_list nml-0.2.4/nml/actions/actionF.py0000644000061700006170000001563312036626442017227 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Code for storing and generating action F """ from nml import expression, grfstrings, generic from nml.actions import base_action # Helper functions to allocate townname IDs # # Numbers available are from 0 to 0x7f (inclusive). # These numbers can be in five states: # - free: Number is available for use. # - named: Number is allocated to represent a name. # - safe numbered: Number is allocated by the user, and is safe to refer to. # - unsafe numbered: Number is allocated by the user, and is not safe to refer to (that is, it is below the point of 'prepare_output') # - invisible: Number is allocated by a final town_name, without attaching a name to it. It is not accessible any more. # Instances of the TownNames class have a 'name' attribute, which can be 'None' (for an invisible number), # a string (for a named number), or a (constant numeric) expression (for a safe/unsafe number). # free_numbers = set(range(0x7f + 1)) #: Free numbers. first_free_id = 0 #: All numbers before this are allocated. named_numbers = {} #: Mapping of names to named numbers. Note that these are always safe to refer to. numbered_numbers = set() #: Safe numbers introduced by the user (without name). def get_free_id(): """Allocate a number from the free_numbers.""" global first_free_id while first_free_id not in free_numbers: first_free_id = first_free_id + 1 if first_free_id >= 0x80: raise generic.ScriptError("Too many town name blocks. Some of these are autogenerated. Please use less town names.") number = first_free_id free_numbers.remove(number) first_free_id = first_free_id + 1 return number town_names_blocks = {} # Mapping of town_names ID number to TownNames instance. class ActionF(base_action.BaseAction): """ Town names action. @ivar name: Name ID of the town_name. @type name: C{None}, L{Identifier}, or L{ConstantNumeric} @ivar id_number: Allocated ID number for this town_name action F node. @type id_number: C{None} or C{int} @ivar style_name: Name of the translated string containing the name of the style, if any. @type style_name: C{None} or L{String} @ivar style_names: List translations of L{style_name}, pairs (languageID, text). @type style_names: C{list} of (C{int}, L{Identifier}) @ivar parts: Parts of the names. @type parts: C{list} of L{TownNamesPart} @ivar free_bit: First available bit above the bits used by this block. @type free_bit: C{None} if unset, else C{int} @ivar pos: Position information of the 'town_names' block. @type pos: L{Position} """ def __init__(self, name, id_number, style_name, parts, pos): self.name = name self.id_number = id_number self.style_name = style_name self.style_names = [] self.parts = parts self.free_bit = None self.pos = pos def prepare_output(self): # Resolve references to earlier townname actions blocks = set() for part in self.parts: blocks.update(part.resolve_townname_id()) # Allocate a number for this action F. if self.name is None or isinstance(self.name, expression.Identifier): self.id_number = get_free_id() if isinstance(self.name, expression.Identifier): if self.name.value in named_numbers: raise generic.ScriptError('Cannot define town name "%s", it is already in use' % self.name, self.pos) named_numbers[self.name.value] = self.id_number # Add name to the set 'safe' names. else: numbered_numbers.add(self.id_number) # Add number to the set of 'safe' numbers. town_names_blocks[self.id_number] = self # Add self to the available blocks. # Ask descendants for the lowest available bit. if len(blocks) == 0: startbit = 0 # No descendants, all bits are free. else: startbit = max(town_names_blocks[block].free_bit for block in blocks) # Allocate random bits to all parts. for part in self.parts: num_bits = part.assign_bits(startbit) startbit += num_bits self.free_bit = startbit if startbit > 32: raise generic.ScriptError("Not enough random bits for the town name generation (%d needed, 32 available)" % startbit, self.pos) # Pull style names if needed. if self.style_name is not None: grfstrings.validate_string(self.style_name) self.style_names = [(lang_id, grfstrings.get_translation(self.style_name, lang_id)) for lang_id in grfstrings.get_translations(self.style_name)] self.style_names.append( (0x7F, grfstrings.get_translation(self.style_name)) ) self.style_names.sort() if len(self.style_names) == 0: raise generic.ScriptError('Style "%s" defined, but no translations found for it' % self.style_name.name.value, self.pos) else: self.style_names = [] # Style names def get_length_styles(self): if len(self.style_names) == 0: return 0 size = 0 for _lang, txt in self.style_names: size += 1 + grfstrings.get_string_size(txt) # Language ID, text return size + 1 # Terminating 0 def write_styles(self, handle): if len(self.style_names) == 0: return for lang, txt in self.style_names: handle.print_bytex(lang) handle.print_string(txt, final_zero = True) handle.newline() handle.print_bytex(0) handle.newline() # Parts def get_length_parts(self): size = 1 # num_parts byte return size + sum(part.get_length() for part in self.parts) def write_parts(self, handle): handle.print_bytex(len(self.parts)) for part in self.parts: part.write(handle) handle.newline() def write(self, handle): handle.start_sprite(2 + self.get_length_styles() + self.get_length_parts()) handle.print_bytex(0xF) handle.print_bytex(self.id_number | (0x80 if len(self.style_names) > 0 else 0)) handle.newline(str(self.name) if self.name is not None else "") self.write_styles(handle) self.write_parts(handle) handle.end_sprite() def skip_action7(self): return False nml-0.2.4/nml/actions/__init__.py0000644000061700006170000000124312036626441017372 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.2.4/nml/actions/action2var_variables.py0000644000061700006170000006267712036626442021756 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop, generic # Use feature 0x12 for towns (accessible via station/house/industry parent scope) varact2vars = 0x13 * [{}] varact2vars60x = 0x13 * [{}] # feature number: 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12 varact2parent_scope = [0x00, 0x01, 0x02, 0x03, 0x12, None, 0x12, 0x12, None, 0x0A, 0x12, None, None, None, None, 0x12, None, None, None] def default_60xvar(name, args, pos, info): """ Function to convert arguments into a variable parameter. This function handles the default case of one argument. @param name: Name of the variable @type name: C{str} @param args: List of passed arguments @type args: C{list} of L{Expression} @param pos: Position information @type pos: L{Position} @param info: Information of the variable, as found in the dictionary @type info: C{dict} @return: A tuple of two values: - Parameter to use for the 60+x variable - List of possible extra parameters that need to be passed via registers @rtype: C{tuple} of (L{Expression}, C{list} C{tuple} of (C{int}, L{Expression})) """ if len(args) != 1: raise generic.ScriptError("'%s'() requires one argument, encountered %d" % (name, len(args)), pos) return (args[0], []) def signextend(var, info): #r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info['size'] - 1)) return expression.BinOp(nmlop.SUB, expression.BinOp(nmlop.XOR, var, m, var.pos), m, var.pos) def muldiv(var, mul, div): var = expression.BinOp(nmlop.MUL, var, expression.ConstantNumeric(mul, var.pos), var.pos) return expression.BinOp(nmlop.DIV, var, expression.ConstantNumeric(div, var.pos), var.pos) varact2_globalvars = { 'current_month' : {'var': 0x02, 'start': 0, 'size': 8}, 'current_day_of_month' : {'var': 0x02, 'start': 8, 'size': 5}, 'is_leapyear' : {'var': 0x02, 'start': 15, 'size': 1}, 'current_day_of_year' : {'var': 0x02, 'start': 16, 'size': 9}, 'climate' : {'var': 0x03, 'start': 0, 'size': 2}, 'traffic_side' : {'var': 0x06, 'start': 0, 'size': 8}, 'animation_counter' : {'var': 0x0A, 'start': 0, 'size': 16}, 'current_callback' : {'var': 0x0C, 'start': 0, 'size': 16}, 'extra_callback_info1' : {'var': 0x10, 'start': 0, 'size': 32}, 'game_mode' : {'var': 0x12, 'start': 0, 'size': 8}, 'extra_callback_info2' : {'var': 0x18, 'start': 0, 'size': 32}, 'display_options' : {'var': 0x1B, 'start': 0, 'size': 6}, 'last_computed_result' : {'var': 0x1C, 'start': 0, 'size': 32}, 'snowline_height' : {'var': 0x20, 'start': 0, 'size': 8}, 'difficulty_level' : {'var': 0x22, 'start': 0, 'size': 8}, 'current_date' : {'var': 0x23, 'start': 0, 'size': 32}, 'current_year' : {'var': 0x24, 'start': 0, 'size': 32}, } def func_add_constant(const): return lambda var, info: expression.BinOp(nmlop.ADD, var, expression.ConstantNumeric(const), var.pos) varact2vars_vehicles = { 'position_in_consist' : {'var': 0x40, 'start': 0, 'size': 8}, 'position_in_consist_from_end' : {'var': 0x40, 'start': 8, 'size': 8}, 'num_vehs_in_consist' : {'var': 0x40, 'start': 16, 'size': 8, 'function': func_add_constant(1)}, 'position_in_vehid_chain' : {'var': 0x41, 'start': 0, 'size': 8}, 'position_in_vehid_chain_from_end' : {'var': 0x41, 'start': 8, 'size': 8}, 'num_vehs_in_vehid_chain' : {'var': 0x41, 'start': 16, 'size': 8, 'function': func_add_constant(1)}, 'cargo_classes_in_consist' : {'var': 0x42, 'start': 0, 'size': 8}, 'most_common_refit' : {'var': 0x42, 'start': 16, 'size': 8}, 'bitmask_consist_info' : {'var': 0x42, 'start': 24, 'size': 8}, 'company_num' : {'var': 0x43, 'start': 0, 'size': 8}, 'company_type' : {'var': 0x43, 'start': 16, 'size': 2}, 'company_colour1' : {'var': 0x43, 'start': 24, 'size': 4}, 'company_colour2' : {'var': 0x43, 'start': 28, 'size': 4}, 'aircraft_height' : {'var': 0x44, 'start': 8, 'size': 8}, 'airport_type' : {'var': 0x44, 'start': 0, 'size': 8}, 'curv_info_prev_cur' : {'var': 0x45, 'start': 0, 'size': 4, 'function': signextend}, 'curv_info_cur_next' : {'var': 0x45, 'start': 8, 'size': 4, 'function': signextend}, 'curv_info_prev_next' : {'var': 0x45, 'start': 16, 'size': 4, 'function': signextend}, 'curv_info' : {'var': 0x45, 'start': 0, 'size': 12, 'function': lambda var, info: expression.BinOp(nmlop.AND, var, expression.ConstantNumeric(0x0F0F, var.pos), var.pos).reduce()}, 'motion_counter' : {'var': 0x46, 'start': 8, 'size': 4}, 'cargo_type_in_veh' : {'var': 0x47, 'start': 0, 'size': 8}, 'cargo_unit_weight' : {'var': 0x47, 'start': 8, 'size': 8}, 'cargo_classes' : {'var': 0x47, 'start': 16, 'size': 16}, 'vehicle_is_available' : {'var': 0x48, 'start': 0, 'size': 1}, 'vehicle_is_testing' : {'var': 0x48, 'start': 1, 'size': 1}, 'vehicle_is_offered' : {'var': 0x48, 'start': 2, 'size': 1}, 'build_year' : {'var': 0x49, 'start': 0, 'size': 32}, 'direction' : {'var': 0x9F, 'start': 0, 'size': 8}, 'cargo_capacity' : {'var': 0xBA, 'start': 0, 'size': 16}, 'cargo_count' : {'var': 0xBC, 'start': 0, 'size': 16}, 'vehicle_type_id' : {'var': 0xC6, 'start': 0, 'size': 16}, 'cargo_subtype' : {'var': 0xF2, 'start': 0, 'size': 8}, 'vehicle_is_powered' : {'var': 0xFE, 'start': 5, 'size': 1}, 'vehicle_is_not_powered' : {'var': 0xFE, 'start': 6, 'size': 1}, 'vehicle_is_potentially_powered': {'var': 0x4A, 'start': 8, 'size': 1}, 'vehicle_is_reversed' : {'var': 0xFE, 'start': 8, 'size': 1}, 'built_during_preview' : {'var': 0xFE, 'start': 10, 'size': 1}, 'current_railtype' : {'var': 0x4A, 'start': 0, 'size': 8}, 'waiting_triggers' : {'var': 0x5F, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, 'grfid' : {'var': 0x25, 'start': 0, 'size': 32}, 'vehicle_is_hidden' : {'var': 0xB2, 'start': 0, 'size': 1}, 'vehicle_is_stopped' : {'var': 0xB2, 'start': 1, 'size': 1}, 'vehicle_is_crashed' : {'var': 0xB2, 'start': 7, 'size': 1}, 'vehicle_is_broken' : {'var': 0xCB, 'start': 0, 'size': 8, 'function': lambda var, info: expression.BinOp(nmlop.CMP_EQ, var, expression.ConstantNumeric(1, var.pos), var.pos)}, 'date_of_last_service' : {'var': 0x4B, 'start': 0, 'size': 32}, 'breakdowns_since_last_service' : {'var': 0xCA, 'start': 0, 'size': 8}, 'reliability' : {'var': 0xCE, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 101, 0x10000)}, 'age_in_days' : {'var': 0xC0, 'start': 0, 'size': 16}, 'max_age_in_days' : {'var': 0xC2, 'start': 0, 'size': 16}, } varact2vars_trains = { #0x4786 / 0x10000 is an approximation of 3.5790976, the conversion factor #for train speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x4786, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x4786, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x4786, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 7, 'size': 1} } varact2vars_trains.update(varact2vars_vehicles) varact2vars_roadvehs = { #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor #for road vehicle speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x23C3, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x23C3, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 0, 'size': 8, 'function': lambda var, info: expression.BinOp(nmlop.CMP_EQ, var, expression.ConstantNumeric(0xFE, var.pos))}, } varact2vars_roadvehs.update(varact2vars_vehicles) varact2vars_ships = { #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor #for ship speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x23C3, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x23C3, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 7, 'size': 1} } varact2vars_ships.update(varact2vars_vehicles) varact2vars_aircraft = { #0x3939 / 0x1000 is an approximation of 0.279617, the conversion factor #Note that the denominator has one less zero here! #for aircraft speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x3939, 0x1000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x3939, 0x1000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'function': lambda var, info: muldiv(var, 0x3939, 0x1000)}, 'vehicle_is_in_depot' : {'var': 0xE6, 'start': 0, 'size': 8, 'function': lambda var, info: expression.BinOp(nmlop.CMP_EQ, var, expression.ConstantNumeric(0, var.pos))}, } varact2vars_aircraft.update(varact2vars_vehicles) def signed_byte_parameter(name, args, pos, info): # Convert to a signed byte by AND-ing with 0xFF if len(args) != 1: raise generic.ScriptError("%s() requires one argument, encountered %d" % (name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, -128, 127, "parameter of %s()" % name, pos) ret = expression.BinOp(nmlop.AND, args[0], expression.ConstantNumeric(0xFF, pos), pos).reduce() return (ret, []) varact2vars60x_vehicles = { 'count_veh_id' : {'var': 0x60, 'start': 0, 'size': 8}, 'other_veh_curv_info' : {'var': 0x62, 'start': 0, 'size': 4, 'param_function':signed_byte_parameter, 'value_function':signextend}, 'other_veh_is_hidden' : {'var': 0x62, 'start': 7, 'size': 1, 'param_function':signed_byte_parameter}, 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':signextend}, 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':signextend}, 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':signextend}, } varact2vars_canals = { 'tile_height' : {'var': 0x80, 'start': 0, 'size': 8, 'function': lambda var, info: muldiv(var, 8, 1)}, 'terrain_type' : {'var': 0x81, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x83, 'start': 0, 'size': 8}, } varact2vars_industrytiles = { 'construction_state' : {'var': 0x40, 'start': 0, 'size': 2}, 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_zone': {'var': 0x42, 'start': 0, 'size': 3}, 'relative_x': {'var': 0x43, 'start': 0, 'size': 8}, 'relative_y': {'var': 0x43, 'start': 8, 'size': 8}, 'relative_pos': {'var': 0x43, 'start': 0, 'size': 16}, 'animation_frame': {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, } def tile_offset(name, args, pos, info, min, max): if len(args) != 2: raise generic.ScriptError("'%s'() requires 2 arguments, encountered %d" % (name, len(args)), pos) for arg in args: if isinstance(arg, expression.ConstantNumeric): generic.check_range(arg.value, min, max, "Argument of '%s'" % name, arg.pos) x = expression.BinOp(nmlop.AND, args[0], expression.ConstantNumeric(0xF), args[0].pos) y = expression.BinOp(nmlop.AND, args[1], expression.ConstantNumeric(0xF), args[1].pos) # Shift y left by four y = expression.BinOp(nmlop.SHIFT_LEFT, y, expression.ConstantNumeric(4), y.pos) param = expression.BinOp(nmlop.ADD, x, y, x.pos) #Make sure to reduce the result return ( param.reduce(), [] ) def signed_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, -8, 7) def unsigned_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, 0, 15) varact2vars60x_industrytiles = { 'nearby_tile_slope' : {'var': 0x60, 'start': 0, 'size': 5, 'function': signed_tile_offset}, 'nearby_tile_is_same_industry' : {'var': 0x60, 'start': 8, 'size': 1, 'function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x60, 'start': 9, 'size': 1, 'function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x60, 'start': 10, 'size': 3, 'function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x60, 'start': 13, 'size': 2, 'function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x60, 'start': 16, 'size': 8, 'function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'function': signed_tile_offset}, 'nearby_tile_industrytile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'function': signed_tile_offset}, } varact2vars_industries = { 'waiting_cargo_1' : {'var': 0x40, 'start': 0, 'size': 16}, 'waiting_cargo_2' : {'var': 0x41, 'start': 0, 'size': 16}, 'waiting_cargo_3' : {'var': 0x42, 'start': 0, 'size': 16}, 'water_distance' : {'var': 0x43, 'start': 0, 'size': 32}, 'layout_num' : {'var': 0x44, 'start': 0, 'size': 8}, # bits 0 .. 16 are either useless or already covered by var A7 'founder_type' : {'var': 0x45, 'start': 16, 'size': 2}, 'founder_colour1' : {'var': 0x45, 'start': 24, 'size': 4}, 'founder_colour2' : {'var': 0x45, 'start': 28, 'size': 4}, 'build_date' : {'var': 0x46, 'start': 0, 'size': 32}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 16}, 'produced_cargo_waiting_1' : {'var': 0x8A, 'start': 0, 'size': 16}, 'produced_cargo_waiting_2' : {'var': 0x8C, 'start': 0, 'size': 16}, 'production_rate_1' : {'var': 0x8E, 'start': 0, 'size': 8}, 'production_rate_2' : {'var': 0x8F, 'start': 0, 'size': 8}, 'production_level' : {'var': 0x93, 'start': 0, 'size': 8}, 'produced_this_month_1' : {'var': 0x94, 'start': 0, 'size': 16}, 'produced_this_month_2' : {'var': 0x96, 'start': 0, 'size': 16}, 'transported_this_month_1' : {'var': 0x98, 'start': 0, 'size': 16}, 'transported_this_month_2' : {'var': 0x9A, 'start': 0, 'size': 16}, 'transported_last_month_pct_1' : {'var': 0x9C, 'start': 0, 'size': 8, 'function': lambda var, info: muldiv(var, 101, 256)}, 'transported_last_month_pct_2' : {'var': 0x9D, 'start': 0, 'size': 8, 'function': lambda var, info: muldiv(var, 101, 256)}, 'produced_last_month_1' : {'var': 0x9E, 'start': 0, 'size': 16}, 'produced_last_month_2' : {'var': 0xA0, 'start': 0, 'size': 16}, 'transported_last_month_1' : {'var': 0xA2, 'start': 0, 'size': 16}, 'transported_last_month_2' : {'var': 0xA4, 'start': 0, 'size': 16}, 'founder' : {'var': 0xA7, 'start': 0, 'size': 8}, 'colour' : {'var': 0xA8, 'start': 0, 'size': 8}, 'counter' : {'var': 0xAA, 'start': 0, 'size': 16}, 'build_type' : {'var': 0xB3, 'start': 0, 'size': 2}, 'last_accept_date' : {'var': 0xB4, 'start':0, 'size': 16, 'function': func_add_constant(701265)} } def industry_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: raise generic.ScriptError("'%s'() requires between 1 and 2 argument(s), encountered %d" % (name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [(0x100, grfid)] return (args[0], extra_params) def industry_layout_count(name, args, pos, info): if len(args) < 2 or len(args) > 3: raise generic.ScriptError("'%s'() requires between 2 and 3 argument(s), encountered %d" % (name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 2 else args[2] extra_params = [] extra_params.append( (0x100, grfid) ) extra_params.append( (0x101, expression.BinOp(nmlop.AND, args[1], expression.ConstantNumeric(0xFF)).reduce()) ) return (args[0], extra_params) def industry_town_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: raise generic.ScriptError("'%s'() requires between 1 and 2 argument(s), encountered %d" % (name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [] extra_params.append( (0x100, grfid) ) extra_params.append( (0x101, expression.ConstantNumeric(0x0100)) ) return (args[0], extra_params) varact2vars60x_industries = { 'nearby_tile_industry_tile_id' : { 'var': 0x60, 'start': 0, 'size': 16, 'function': unsigned_tile_offset }, 'nearby_tile_random_bits' : { 'var': 0x61, 'start': 0, 'size': 8, 'function': unsigned_tile_offset }, 'nearby_tile_slope' : { 'var': 0x62, 'start': 0, 'size': 5, 'function': unsigned_tile_offset }, 'nearby_tile_is_water' : { 'var': 0x62, 'start': 9, 'size': 1, 'function': unsigned_tile_offset }, 'nearby_tile_terrain_type' : { 'var': 0x62, 'start': 10, 'size': 3, 'function': unsigned_tile_offset }, 'nearby_tile_water_class' : { 'var': 0x62, 'start': 13, 'size': 2, 'function': unsigned_tile_offset }, 'nearby_tile_height' : { 'var': 0x62, 'start': 16, 'size': 8, 'function': unsigned_tile_offset }, 'nearby_tile_class' : { 'var': 0x62, 'start': 24, 'size': 4, 'function': unsigned_tile_offset }, 'nearby_tile_animation_frame' : { 'var': 0x63, 'start': 0, 'size': 8, 'function': unsigned_tile_offset }, 'town_manhattan_dist' : { 'var': 0x65, 'start': 0, 'size': 16, 'function': signed_tile_offset }, 'town_zone' : { 'var': 0x65, 'start': 16, 'size': 8, 'function': signed_tile_offset }, 'town_euclidean_dist' : { 'var': 0x66, 'start': 0, 'size': 32, 'function': signed_tile_offset }, 'industry_count' : { 'var': 0x67, 'start': 16, 'size': 8, 'function': industry_count }, 'industry_distance' : { 'var': 0x67, 'start': 0, 'size': 16, 'function': industry_count }, 'industry_layout_count' : { 'var': 0x68, 'start': 16, 'size': 8, 'function': industry_layout_count }, 'industry_layout_distance' : { 'var': 0x68, 'start': 0, 'size': 16, 'function': industry_layout_count }, 'industry_town_count' : { 'var': 0x68, 'start': 16, 'size': 8, 'function': industry_town_count }, } varact2vars_airports = { 'layout' : {'var': 0x40, 'start': 0, 'size': 32}, } varact2vars_objects = { 'relative_x' : {'var': 0x40, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x40, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x40, 'start': 0, 'size': 16}, 'terrain_type' : { 'var' : 0x41, 'start': 0, 'size': 3 }, 'tile_slope' : { 'var' : 0x41, 'start': 8, 'size': 5 }, 'build_date' : { 'var' : 0x42, 'start': 0, 'size': 32 }, 'animation_frame' : { 'var' : 0x43, 'start': 0, 'size': 8 }, 'company_colour' : { 'var' : 0x43, 'start': 8, 'size': 8 }, 'owner' : { 'var' : 0x44, 'start': 0, 'size': 8 }, 'town_manhattan_dist' : { 'var' : 0x45, 'start': 0, 'size': 16 }, 'town_zone' : { 'var' : 0x45, 'start': 16, 'size': 8 }, 'town_euclidean_dist' : { 'var' : 0x46, 'start': 0, 'size': 32 }, 'view' : { 'var' : 0x48, 'start': 0, 'size': 8 }, 'random_bits' : { 'var' : 0x5F, 'start': 8, 'size': 8 }, } varact2vars60x_objects = { 'nearby_tile_object_type' : { 'var' : 0x60, 'start': 0, 'size': 16, 'function': signed_tile_offset }, 'nearby_tile_random_bits' : { 'var' : 0x61, 'start': 0, 'size': 8, 'function': signed_tile_offset }, 'nearby_tile_slope' : { 'var' : 0x62, 'start': 0, 'size': 5, 'function': signed_tile_offset }, 'nearby_tile_is_same_object' : { 'var' : 0x62, 'start': 8, 'size': 1, 'function': signed_tile_offset }, 'nearby_tile_is_water' : { 'var' : 0x62, 'start': 9, 'size': 1, 'function': signed_tile_offset }, 'nearby_tile_terrain_type' : { 'var' : 0x62, 'start': 10, 'size': 3, 'function': signed_tile_offset }, 'nearby_tile_water_class' : { 'var' : 0x62, 'start': 13, 'size': 2, 'function': signed_tile_offset }, 'nearby_tile_height' : { 'var' : 0x62, 'start': 16, 'size': 8, 'function': signed_tile_offset }, 'nearby_tile_class' : { 'var' : 0x62, 'start': 24, 'size': 4, 'function': signed_tile_offset }, 'nearby_tile_animation_frame' : { 'var' : 0x63, 'start': 0, 'size': 8, 'function': signed_tile_offset }, 'object_count' : { 'var' : 0x64, 'start': 16, 'size': 8, 'function': industry_count }, 'object_distance' : { 'var' : 0x64, 'start': 0, 'size': 16, 'function': industry_count }, } varact2vars_railtype = { 'terrain_type' : {'var': 0x40, 'start': 0, 'size': 8}, 'enhanced_tunnels' : {'var': 0x41, 'start': 0, 'size': 8}, 'level_crossing_status' : {'var': 0x42, 'start': 0, 'size': 8}, 'build_date' : {'var': 0x43, 'start': 0, 'size': 32}, 'town_zone' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } varact2vars_airporttiles = { 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_radius_group': {'var': 0x42, 'start': 0, 'size': 3}, 'relative_x': {'var': 0x43, 'start': 0, 'size': 8}, 'relative_y': {'var': 0x43, 'start': 8, 'size': 8}, 'relative_pos': {'var': 0x43, 'start': 0, 'size': 16}, 'animation_frame': {'var': 0x44, 'start': 0, 'size': 8}, } varact2vars60x_airporttiles = { 'nearby_tile_slope' : {'var': 0x60, 'start': 0, 'size': 5, 'function': signed_tile_offset}, 'nearby_tile_is_same_airport' : {'var': 0x60, 'start': 8, 'size': 1, 'function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x60, 'start': 9, 'size': 1, 'function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x60, 'start': 10, 'size': 3, 'function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x60, 'start': 13, 'size': 2, 'function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x60, 'start': 16, 'size': 8, 'function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'function': signed_tile_offset}, 'nearby_tile_airporttile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'function': signed_tile_offset}, } varact2vars_towns = { 'is_city' : {'var': 0x40, 'start': 0, 'size': 1}, 'cities_enabled' : {'var': 0x40, 'start': 1, 'size': 1, 'function': lambda var, info: expression.Not(var, var.pos)}, 'population' : {'var': 0x82, 'start': 0, 'size': 16}, 'has_church' : {'var': 0x92, 'start': 1, 'size': 1}, 'has_stadium' : {'var': 0x92, 'start': 2, 'size': 1}, 'town_zone_0_radius_square' : {'var': 0x94, 'start': 0, 'size': 16}, 'town_zone_1_radius_square' : {'var': 0x96, 'start': 0, 'size': 16}, 'town_zone_2_radius_square' : {'var': 0x98, 'start': 0, 'size': 16}, 'town_zone_3_radius_square' : {'var': 0x9A, 'start': 0, 'size': 16}, 'town_zone_4_radius_square' : {'var': 0x9C, 'start': 0, 'size': 16}, 'num_houses' : {'var': 0xB6, 'start': 0, 'size': 16}, 'percent_transported_passengers' : {'var': 0xCA, 'start': 0, 'size': 8, 'function': lambda var, info: muldiv(var, 101, 256)}, 'percent_transported_mail' : {'var': 0xCB, 'start': 0, 'size': 8, 'function': lambda var, info: muldiv(var, 101, 256)}, } varact2vars[0x00] = varact2vars_trains varact2vars60x[0x00] = varact2vars60x_vehicles varact2vars[0x01] = varact2vars_roadvehs varact2vars60x[0x01] = varact2vars60x_vehicles varact2vars[0x02] = varact2vars_ships varact2vars60x[0x02] = varact2vars60x_vehicles varact2vars[0x03] = varact2vars_aircraft varact2vars60x[0x03] = varact2vars60x_vehicles varact2vars[0x05] = varact2vars_canals varact2vars[0x09] = varact2vars_industrytiles varact2vars60x[0x09] = varact2vars60x_industrytiles varact2vars[0x0A] = varact2vars_industries varact2vars60x[0x0A] = varact2vars60x_industries varact2vars[0x0D] = varact2vars_airports varact2vars[0x0F] = varact2vars_objects varact2vars60x[0x0F] = varact2vars60x_objects varact2vars[0x10] = varact2vars_railtype varact2vars[0x11] = varact2vars_airporttiles varact2vars60x[0x11] = varact2vars60x_airporttiles varact2vars[0x12] = varact2vars_towns nml-0.2.4/nml/actions/action12.py0000644000061700006170000000613612036626442017262 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression from nml.actions import base_action, real_sprite class Action12(base_action.BaseAction): #sets: list of (font_size, num_char, base_char) tuples def __init__(self, sets): self.sets = sets def write(self, file): # * 12 ( ){n} size = 2 + 4 * len(self.sets) file.start_sprite(size) file.print_bytex(0x12) file.print_byte(len(self.sets)) file.newline() for font_size, num_char, base_char in self.sets: font_size.write(file, 1) file.print_byte(num_char) file.print_word(base_char) file.newline() file.end_sprite() font_sizes = { 'NORMAL' : 0, 'SMALL' : 1, 'LARGE' : 2, 'MONO' : 3, } def parse_action12(font_glyphs): try: font_size = font_glyphs.font_size.reduce_constant([font_sizes]) if isinstance(font_glyphs.base_char, expression.StringLiteral) and len(font_glyphs.base_char.value) == 1: base_char = ord(font_glyphs.base_char.value) else: base_char = font_glyphs.base_char.reduce_constant() except generic.ConstError: raise generic.ScriptError("Parameters of font_glpyh have to be compile-time constants", font_glyphs.pos) if font_size.value not in font_sizes.values(): raise generic.ScriptError("Invalid value for parameter 'font_size' in font_glpyh, valid values are 0, 1, 2", font_size.pos) if not (0 <= base_char.value <= 0xFFFF): raise generic.ScriptError("Invalid value for parameter 'base_char' in font_glyph, valid values are 0-0xFFFF", base_char.pos) real_sprite_list = real_sprite.parse_sprite_list(font_glyphs.sprite_list, font_glyphs.pcx, block_name = font_glyphs.name) char = base_char.value last_char = char + len(real_sprite_list) if last_char > 0xFFFF: raise generic.ScriptError("Character numbers in font_glyph block exceed the allowed range (0-0xFFFF)", font_glyphs.pos) sets = [] while char < last_char: #each set of characters must fit in a single 128-char block, according to specs / TTDP if (char // 128) * 128 != (last_char // 128) * 128: num_in_set = (char // 128 + 1) * 128 - char else: num_in_set = last_char - char sets.append((font_size, num_in_set, char)) char += num_in_set return [Action12(sets)] + real_sprite_list nml-0.2.4/nml/actions/actionD.py0000644000061700006170000004220012036626442017213 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression, nmlop from nml.actions import base_action, action6 from nml.ast import base_statement import nml class ActionD(base_action.BaseAction): """ ActionD class General procedure: target = param1 op param2 If one of the params is 0xFF, the value of 'data' is read instead. @ivar target: Number of the target parameter @ivar target: L{ConstantNumeric} @ivar param1: Paramter number of the first operand @type param1: L{ConstantNumeric} @ivar op: (Binary) operator to use. @type op: L{Operator} @ivar param2: Paramter number of the second operand @type param2: L{ConstantNumeric} @ivar data: Numerical data that will be used instead of parameter value, if the parameter number is 0xFF. None if n/a. @type data: L{ConstantNumeric} or C{None} """ def __init__(self, target, param1, op, param2, data = None): self.target = target self.param1 = param1 self.op = op self.param2 = param2 self.data = data def write(self, file): size = 5 if self.data is not None: size += 4 #print the statement for easier debugging str1 = "param[%s]" % self.param1 if self.param1.value != 0xFF else str(self.data) str2 = "param[%s]" % self.param2 if self.param2.value != 0xFF else str(self.data) str_total = self.op.to_string(str1, str2) if self.op != nmlop.ASSIGN else str1 file.comment("param[%s] = %s" % (self.target, str_total)) file.start_sprite(size) file.print_bytex(0x0D) self.target.write(file, 1) file.print_bytex(self.op.actd_num, self.op.actd_str) self.param1.write(file, 1) self.param2.write(file, 1) if self.data is not None: self.data.write(file, 4) file.newline() file.end_sprite() def skip_action7(self): return False class ParameterAssignment(base_statement.BaseStatement): """ AST-node for a parameter assignment. NML equivalent: param[$num] = $expr; @ivar param: Target expression to assign (must evaluate to a parameter) @type param: L{Expression} @ivar value: Value to assign to this parameter @type value: L{Expression} """ def __init__(self, param, value): base_statement.BaseStatement.__init__(self, "parameter assignment", param.pos) self.param = param self.value = value def pre_process(self): self.value = self.value.reduce(global_constants.const_list) self.param = self.param.reduce(global_constants.const_list, unknown_id_fatal = False) if isinstance(self.param, expression.SpecialParameter): if not self.param.can_assign(): raise generic.ScriptError("Trying to assign a value to the read-only variable '%s'" % self.param.name, self.param.pos) elif isinstance(self.param, expression.Identifier): num = action6.free_parameters.pop_unique() global_constants.named_parameters[self.param.value] = num elif not isinstance(self.param, expression.Parameter): raise generic.ScriptError("Left side of an assignment must be a parameter.", self.param.pos) def debug_print(self, indentation): print indentation*' ' + 'Parameter assignment' self.param.debug_print(indentation + 2) self.value.debug_print(indentation + 2) def get_action_list(self): return parse_actionD(self) def __str__(self): return '%s = %s;\n' % (str(self.param), str(self.value)) #prevent evaluating common sub-expressions multiple times def parse_subexpression(expr, action_list): if isinstance(expr, expression.ConstantNumeric) or \ (isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric)): return expr else: tmp_param, tmp_param_actions = get_tmp_parameter(expr) action_list.extend(tmp_param_actions) return expression.Parameter(expression.ConstantNumeric(tmp_param)) #returns a (param_num, action_list) tuple. def get_tmp_parameter(expr): param = action6.free_parameters.pop() actions = parse_actionD(ParameterAssignment(expression.Parameter(expression.ConstantNumeric(param)), expr)) return (param, actions) def parse_ternary_op(assignment): assert isinstance(assignment.value, expression.TernaryOp) actions = parse_actionD(ParameterAssignment(assignment.param, assignment.value.expr2)) cond_block = nml.ast.conditional.Conditional(assignment.value.guard, [ParameterAssignment(assignment.param, assignment.value.expr1)], None) actions.extend(nml.ast.conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_special_check(assignment): check = assignment.value assert isinstance(check, expression.SpecialCheck) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(check.results[0]))) value = check.value size = 4 if check.mask is not None: value &= check.mask value += check.mask << 32 size = 8 actions.append(nml.actions.action7.SkipAction(9, check.varnum, size, check.op, value, 1)) actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(check.results[1])))) return actions def parse_grm(assignment): assert isinstance(assignment.value, expression.GRMOp) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = assignment.value.op param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFF | (assignment.value.feature << 8) | (assignment.value.count << 16)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list def parse_hasbit(assignment): assert isinstance(assignment.value, expression.BinOp) and (assignment.value.op == nmlop.HASBIT or assignment.value.op == nmlop.NOTHASBIT) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(0))) cond_block = nml.ast.conditional.Conditional(assignment.value, [ParameterAssignment(assignment.param, expression.ConstantNumeric(1))], None) actions.extend(nml.ast.conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_min_max(assignment): assert isinstance(assignment.value, expression.BinOp) and assignment.value.op in (nmlop.MIN, nmlop.MAX) #min(a, b) ==> a < b ? a : b. #max(a, b) ==> a > b ? a : b. action6.free_parameters.save() action_list = [] expr1 = parse_subexpression(assignment.value.expr1, action_list) expr2 = parse_subexpression(assignment.value.expr2, action_list) guard = expression.BinOp(nmlop.CMP_LT if assignment.value.op == nmlop.MIN else nmlop.CMP_GT, expr1, expr2) action_list.extend(parse_actionD(ParameterAssignment(assignment.param, expression.TernaryOp(guard, expr1, expr2, None)))) action6.free_parameters.restore() return action_list def parse_boolean(assignment): assert isinstance(assignment.value, expression.Boolean) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(0))) expr = expression.BinOp(nmlop.CMP_NEQ, assignment.value.expr, expression.ConstantNumeric(0)) cond_block = nml.ast.conditional.Conditional(expr, [ParameterAssignment(assignment.param, expression.ConstantNumeric(1))], None) actions.extend(nml.ast.conditional.ConditionalList([cond_block]).get_action_list()) return actions def transform_bin_op(assignment): op = assignment.value.op expr1 = assignment.value.expr1 expr2 = assignment.value.expr2 extra_actions = [] if op == nmlop.CMP_GE: expr1, expr2 = expr2, expr1 op = nmlop.CMP_LE if op == nmlop.CMP_LE: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) op = nmlop.CMP_LT expr1 = assignment.param expr2 = expression.ConstantNumeric(1) if op == nmlop.CMP_GT: expr1, expr2 = expr2, expr1 op = nmlop.CMP_LT if op == nmlop.CMP_LT: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) op = nmlop.SHIFTU_LEFT #shift left by negative number = shift right expr1 = assignment.param expr2 = expression.ConstantNumeric(-31) elif op == nmlop.CMP_NEQ: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) op = nmlop.DIV # We rely here on the (ondocumented) behavior of both OpenTTD and TTDPatch # that expr/0==expr. What we do is compute A/A, which will result in 1 if # A != 0 and in 0 if A == 0 expr1 = assignment.param expr2 = assignment.param elif op == nmlop.CMP_EQ: # We compute A==B by doing not(A - B) which will result in a value != 0 # if A is equal to B extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) # Clamp the value to 0/1, see above for details extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.DIV, assignment.param, assignment.param)))) op = nmlop.SUB expr1 = expression.ConstantNumeric(1) expr2 = assignment.param if op == nmlop.SHIFT_RIGHT or op == nmlop.SHIFTU_RIGHT: if isinstance(expr2, expression.ConstantNumeric): expr2.value *= -1 else: expr2 = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(0), expr2) op = nmlop.SHIFT_LEFT if op == nmlop.SHIFT_RIGHT else nmlop.SHIFTU_LEFT elif op == nmlop.XOR: #a ^ b ==> (a | b) - (a & b) expr1 = parse_subexpression(expr1, extra_actions) expr2 = parse_subexpression(expr2, extra_actions) tmp_param1, tmp_action_list1 = get_tmp_parameter(expression.BinOp(nmlop.OR, expr1, expr2)) tmp_param2, tmp_action_list2 = get_tmp_parameter(expression.BinOp(nmlop.AND, expr1, expr2)) extra_actions.extend(tmp_action_list1) extra_actions.extend(tmp_action_list2) expr1 = expression.Parameter(expression.ConstantNumeric(tmp_param1)) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param2)) op = nmlop.SUB return op, expr1, expr2, extra_actions def parse_actionD(assignment): assignment.value.supported_by_actionD(True) if isinstance(assignment.param, expression.SpecialParameter): assignment.param, assignment.value = assignment.param.to_assignment(assignment.value) elif isinstance(assignment.param, expression.Identifier): assignment.param = expression.Parameter(expression.ConstantNumeric(global_constants.named_parameters[assignment.param.value]), assignment.param.pos) assert isinstance(assignment.param, expression.Parameter) if isinstance(assignment.value, expression.SpecialParameter): assignment.value = assignment.value.to_reading() if isinstance(assignment.value, expression.TernaryOp): return parse_ternary_op(assignment) if isinstance(assignment.value, expression.SpecialCheck): return parse_special_check(assignment) if isinstance(assignment.value, expression.GRMOp): return parse_grm(assignment) if isinstance(assignment.value, expression.BinOp): op = assignment.value.op if op == nmlop.HASBIT or op == nmlop.NOTHASBIT: return parse_hasbit(assignment) elif op == nmlop.MIN or op == nmlop.MAX: return parse_min_max(assignment) if isinstance(assignment.value, expression.Boolean): return parse_boolean(assignment) if isinstance(assignment.value, expression.Not): expr = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(1), assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) if isinstance(assignment.value, expression.BinNot): expr = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(0xFFFFFFFF), assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) data = None #print assignment.value if isinstance(assignment.value, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0xFF) param2 = expression.ConstantNumeric(0) data = assignment.value elif isinstance(assignment.value, expression.Parameter): if isinstance(assignment.value.num, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0) elif isinstance(assignment.value, expression.OtherGRFParameter): op = nmlop.ASSIGN if isinstance(assignment.value.num, expression.ConstantNumeric): param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(expression.parse_string_to_dword(assignment.value.grfid)) elif isinstance(assignment.value, expression.PatchVariable): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(assignment.value.num) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFFFF) elif isinstance(assignment.value, expression.BinOp): op, expr1, expr2, extra_actions = transform_bin_op(assignment) action_list.extend(extra_actions) if isinstance(expr1, expression.ConstantNumeric): param1 = expression.ConstantNumeric(0xFF) data = expr1 elif isinstance(expr1, expression.Parameter) and isinstance(expr1.num, expression.ConstantNumeric): param1 = expr1.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr1) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(tmp_param) # We can use the data only for one for the parameters. # If the first parameter uses "data" we need a temp parameter for this one if isinstance(expr2, expression.ConstantNumeric) and data is None: param2 = expression.ConstantNumeric(0xFF) data = expr2 elif isinstance(expr2, expression.Parameter) and isinstance(expr2.num, expression.ConstantNumeric): param2 = expr2.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr2) action_list.extend(tmp_param_actions) param2 = expression.ConstantNumeric(tmp_param) else: raise generic.ScriptError("Invalid expression in argument assignment", assignment.value.pos) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list nml-0.2.4/nml/actions/action7.py0000644000061700006170000002655212036626442017212 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop, free_number_list from nml.actions import base_action, action6, actionD, action10 free_labels = free_number_list.FreeNumberList(list(range(0xFF, 0x0F, -1))) class SkipAction(base_action.BaseAction): def __init__(self, action_type, var, varsize, condtype, value, label): self.action_type = action_type self.label = label self.var = var self.varsize = varsize self.condtype = condtype self.value = value self.label = label def write(self, file): size = 5 + self.varsize file.start_sprite(size) file.print_bytex(self.action_type) file.print_bytex(self.var) file.print_bytex(self.varsize) file.print_bytex(self.condtype[0], self.condtype[1]) if self.varsize == 8: #grfid + mask file.print_dwordx(self.value & 0xFFFFFFFF) file.print_dwordx(self.value >> 32) else: file.print_varx(self.value, self.varsize) file.print_bytex(self.label) file.newline() file.end_sprite() def skip_action7(self): return self.action_type == 7 def skip_action9(self): return self.action_type == 9 or self.label == 0 class UnconditionalSkipAction(SkipAction): def __init__(self, action_type, label): SkipAction.__init__(self, action_type, 0x9A, 1, (0, r'\71'), 0, label) def op_to_cond_op(op): #The operators are reversed as we want to skip if the expression is true #while the nml-syntax wants to execute the block if the expression is true if op == nmlop.CMP_NEQ: return (2, r'\7=') if op == nmlop.CMP_EQ: return (3, r'\7!') if op == nmlop.CMP_GE: return (4, r'\7<') if op == nmlop.CMP_LE: return (5, r'\7>') def parse_conditional(expr): ''' Parse an expression and return enough information to use that expression as a conditional statement. Return value is a tuple with the following elements: - Parameter number (as integer) to use in comparison or None for unconditional skip - List of actions needed to set the given parameter to the correct value - The type of comparison to be done - The value to compare against (as integer) - The size of the value (as integer) ''' if expr is None: return (None, [], (2, r'\7='), 0, 4) if isinstance(expr, expression.BinOp): if expr.op == nmlop.HASBIT or expr.op == nmlop.NOTHASBIT: if isinstance(expr.expr1, expression.Parameter) and isinstance(expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) if isinstance(expr.expr2, expression.ConstantNumeric): bit_num = expr.expr2.value else: if isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric): param = expr.expr2.num.value else: param, tmp_action_list = actionD.get_tmp_parameter(expr.expr2) actions.extend(tmp_action_list) act6 = action6.Action6() act6.modify_bytes(param, 1, 4) actions.append(act6) bit_num = 0 comp_type = (1, r'\70') if expr.op == nmlop.HASBIT else (0, r'\71') return (param, actions, comp_type, bit_num , 1) elif expr.op in (nmlop.CMP_EQ, nmlop.CMP_NEQ, nmlop.CMP_LE, nmlop.CMP_GE) \ and isinstance(expr.expr2, expression.ConstantNumeric): if isinstance(expr.expr1, expression.Parameter) and isinstance(expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) op = op_to_cond_op(expr.op) return (param, actions, op, expr.expr2.value, 4) if isinstance(expr, expression.Boolean): expr = expr.expr if isinstance(expr, expression.Not): param, actions = actionD.get_tmp_parameter(expr.expr) return (param, actions, (3, r'\7!'), 0, 4) param, actions = actionD.get_tmp_parameter(expr) return (param, actions, (2, r'\7='), 0, 4) def cond_skip_actions(action_list, param, condtype, value, value_size): if len(action_list) == 0: return [] actions = [] start, length = 0, 0 # Whether to allow not-skipping, using action7 or using action9 skip_opts = (True, True, True) # Add a sentinel value to the list to avoid code duplication for action in action_list + [None]: assert any(skip_opts) if action is not None: # Options allowed by the next action act_opts = (not action.skip_needed(), action.skip_action7(), action.skip_action9()) else: # There are no further actions, so be sure to finish the current block act_opts = (False, False, False) # Options still allowed, when including this action in the previous block new_opts = tuple(all(tpl) for tpl in zip(skip_opts, act_opts)) # End this block in two cases: # - There are no options to skip this action and the preceding block in one go # - The existing block (with length > 0) needed no skipping, but this action does if any(new_opts) and not (length > 0 and skip_opts[0] and not new_opts[0]): # Append this action to the existing block length += 1 skip_opts = new_opts continue # We need to create a new block if skip_opts[0]: # We can just choose to not skip the preceeding actions without harm actions.extend(action_list[start:start+length]) else: action_type = 7 if skip_opts[1] else 9 if length < 0x10: # Lengths under 0x10 are handled without labels, to avoid excessive label usage target = length label = None else: target = free_labels.pop() label = action10.Action10(target) actions.append(SkipAction(action_type, param, value_size, condtype, value, target)) actions.extend(action_list[start:start+length]) if label is not None: actions.append(label) start = start + length length = 1 skip_opts = act_opts assert start == len(action_list) return actions recursive_cond_blocks = 0 def parse_conditional_block(cond_list): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: # We only save a single state (at toplevel nml-blocks) because # we don't know at the start of the block how many labels we need. # Getting the same label for a block that was already used in a # sub-block would be very bad, since the action7/9 would skip # to the action10 of the sub-block. free_labels.save() blocks = [] for cond in cond_list.statements: if isinstance(cond.expr, expression.ConstantNumeric): if cond.expr.value == 0: continue else: blocks.append({'expr': None, 'statements': cond.statements}) break blocks.append({'expr': cond.expr, 'statements': cond.statements}) if blocks: blocks[-1]['last_block'] = True if len(blocks) == 1 and blocks[0]['expr'] is None: action_list = [] for stmt in blocks[0]['statements']: action_list.extend(stmt.get_action_list()) return action_list action6.free_parameters.save() if len(blocks) > 1: # the skip all parameter is used to skip all blocks after one # of the conditionals was true. We can't always skip directly # to the end of the blocks since action7/action9 can't always # be mixed param_skip_all, action_list = actionD.get_tmp_parameter(expression.ConstantNumeric(0xFFFFFFFF)) else: action_list = [] # use parse_conditional here, we also need to know if all generated # actions (like action6) can be skipped safely for block in blocks: block['param_dst'], block['cond_actions'], block['cond_type'], block['cond_value'], block['cond_value_size'] = parse_conditional(block['expr']) if not 'last_block' in block: block['action_list'] = [actionD.ActionD(expression.ConstantNumeric(param_skip_all), expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0), expression.ConstantNumeric(0))] else: block['action_list'] = [] for stmt in block['statements']: block['action_list'].extend(stmt.get_action_list()) # Main problem: action10 can't be skipped by action9, so we're # nearly forced to use action7, but action7 can't safely skip action6 # Solution: use temporary parameter, set to 0 for not skip, !=0 for skip. # then skip every block of actions (as large as possible) with either # action7 or action9, depending on which of the two works. for i, block in enumerate(blocks): param = block['param_dst'] if i == 0: action_list.extend(block['cond_actions']) else: action_list.extend(cond_skip_actions(block['cond_actions'], param_skip_all, (2, r'\7='), 0, 4)) if param is None: param = param_skip_all else: action_list.append(actionD.ActionD(expression.ConstantNumeric(block['param_dst']), expression.ConstantNumeric(block['param_dst']), nmlop.AND, expression.ConstantNumeric(param_skip_all))) action_list.extend(cond_skip_actions(block['action_list'], param, block['cond_type'], block['cond_value'], block['cond_value_size'])) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list def parse_loop_block(loop): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: free_labels.save() action6.free_parameters.save() begin_label = free_labels.pop_unique() action_list = [action10.Action10(begin_label)] cond_param, cond_actions, cond_type, cond_value, cond_value_size = parse_conditional(loop.expr) block_actions = [] for stmt in loop.statements: block_actions.extend(stmt.get_action_list()) action_list.extend(cond_actions) block_actions.append(UnconditionalSkipAction(9, begin_label)) action_list.extend(cond_skip_actions(block_actions, cond_param, cond_type, cond_value, cond_value_size)) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list nml-0.2.4/nml/actions/action3.py0000644000061700006170000004073312036626442017203 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, global_constants from nml.actions import base_action, action0, action2, action2real, action2var, action3_callbacks, action6, actionD class Action3(base_action.BaseAction): """ Class representing a single Action3. @ivar feature: Action3 feature byte. @type feature: C{int} @ivar id: Item ID of the item that this action3 represents. @type id: C{int} @ivar cid_mappings: List of mappings that map cargo IDs to Action2s. @type cid_mappings: C{list} of C{tuple}: (C{int}, L{Expression} before prepare_output, C{int} afterwards, C{str}) @ivar def_cid: Default Action2 to use if no cargo ID matches. @type def_cid: C{None} or L{SpriteGroupRef} before prepare_output, C{int} afterwards @ivar references: All Action2s that are referenced by this Action3. @type references: C{list} of L{Action2Reference} """ def __init__(self, feature, id): self.feature = feature self.id = id self.cid_mappings = [] self.references = [] def prepare_output(self): action2.free_references(self) map_cid = lambda cid: cid.get_action2_id(self.feature) if isinstance(cid, expression.SpriteGroupRef) else cid.value | 0x8000 self.cid_mappings = [(cargo, map_cid(cid), comment) for cargo, cid, comment in self.cid_mappings] if self.def_cid is None: self.def_cid = 0 else: self.def_cid = map_cid(self.def_cid) def write(self, file): size = 7 + 3 * len(self.cid_mappings) if self.feature <= 3: size += 2 # Vehicles use extended byte file.start_sprite(size) file.print_bytex(3) file.print_bytex(self.feature) file.print_bytex(1 if not self.is_livery_override else 0x81) # a single id file.print_varx(self.id, 3 if self.feature <= 3 else 1) file.print_byte(len(self.cid_mappings)) file.newline() for cargo, cid, comment in self.cid_mappings: file.print_bytex(cargo) file.print_wordx(cid) file.newline(comment) file.print_wordx(self.def_cid) file.newline(self.default_comment) file.end_sprite() def skip_action9(self): return False # Make sure all action2s created here get a unique name action2_id = 0 def create_intermediate_varaction2(feature, varact2parser, mapping, default): """ Create a varaction2 based on a parsed expression and a value mapping @param feature: Feature of the varaction2 @type feature: C{int} @param varact2parser: Parser containing a parsed expression @type varact2parser: L{Varaction2Parser} @param mapping: Mapping of various values to sprite groups / return values, with a possible extra function to apply to the return value @type mapping: C{dict} that maps C{int} to C{tuple} of (L{SpriteGroupRef}, C{function}, or C{None}) @param default: Default sprite group if no value matches @type default: L{SpriteGroupRef} @return: A tuple containing the action list and a reference to the created action2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ global action2_id action_list = varact2parser.extra_actions act6 = action6.Action6() for mod in varact2parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + 4) name = expression.Identifier("@action3_%d" % action2_id) action2_id += 1 varaction2 = action2var.Action2Var(feature, name.value, 0x89) varaction2.var_list = varact2parser.var_list offset = 5 + varact2parser.var_list_size for proc in varact2parser.proc_call_list: action2.add_ref(proc, varaction2, True) for switch_value in sorted(mapping): return_value, ret_value_function = mapping[switch_value] if ret_value_function is None: result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) else: if isinstance(return_value, expression.SpriteGroupRef): # We need to execute the callback via a procedure call # then return CB_FAILED if the CB failed, # or the CB result (with ret_value_function applied) if successful if return_value.name.value == 'CB_FAILED': result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) else: extra_actions, result, comment = create_proc_call_varaction2(feature, return_value, ret_value_function) action_list.extend(extra_actions) else: return_value = ret_value_function(return_value).reduce() result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(switch_value), expression.ConstantNumeric(switch_value), result, comment)) offset += 10 result, comment = action2var.parse_result(default, action_list, act6, offset, varaction2, None, 0x89) varaction2.default_result = result varaction2.default_comment = comment return_ref = expression.SpriteGroupRef(name, [], None, varaction2) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) return (action_list, return_ref) def create_proc_call_varaction2(feature, proc, ret_value_function): """ Create a varaction2 that executes a procedure call and applies a function on the result @param feature: Feature of the varaction2 @type feature: C{int} @param proc: Procedure to execute @type proc: L{SpriteGroupRef} @param ret_value_function: Function to apply on the result (L{Expression} -> L{Expression}) @type ret_value_function: C{function} @return: A list of extra actions, reference to the created action2 and a comment to add @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}, C{str}) """ varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_proc_call(proc) mapping = {0xFFFF: (expression.SpriteGroupRef(expression.Identifier('CB_FAILED'), [], None), None)} default = ret_value_function(expression.Variable(expression.ConstantNumeric(0x1C))) action_list, result = create_intermediate_varaction2(feature, varact2parser, mapping, default) comment = result.name.value + ';' return (action_list, result, comment) def create_cb_choice_varaction2(feature, var_num, and_mask, mapping, default): """ Create a varaction2 that maps callback numbers to various sprite groups @param feature: Feature of the varaction2 @type feature: C{int} @param var_num: Number of the variable to evaluate @type var_num: C{int} @param and_mask: And-mask to use for the variable @type and_mask: C{int} @param mapping: Mapping of various values to sprite groups, with a possible extra function to apply to the return value @type mapping: C{dict} that maps C{int} to C{tuple} of (L{SpriteGroupRef}, C{function}, or C{None}) @param default: Default sprite group if no value matches @type default: L{SpriteGroupRef} @return: A tuple containing the action list and a reference to the created action2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(expression.Variable(expression.ConstantNumeric(var_num), mask = expression.ConstantNumeric(and_mask))) return create_intermediate_varaction2(feature, varact2parser, mapping, default) def create_action3(feature, id, action_list, act6, is_livery_override): if isinstance(id, expression.ConstantNumeric): act3 = Action3(feature, id.value) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) # Vehicles use an extended byte size = 2 if feature <= 3 else 1 offset = 4 if feature <= 3 else 3 act6.modify_bytes(tmp_param, size, offset) action_list.extend(tmp_param_actions) act3 = Action3(feature, 0) act3.is_livery_override = is_livery_override return act3 def parse_graphics_block(graphics_block, feature, id, is_livery_override = False): action6.free_parameters.save() prepend_action_list = action2real.create_spriteset_actions(graphics_block) action_list = [] act6 = action6.Action6() act3 = create_action3(feature, id, action_list, act6, is_livery_override) cargo_gfx = {} seen_callbacks = set() callbacks = [] livery_override = None # Used for rotor graphics for graphics in graphics_block.graphics_list: cargo_id = graphics.cargo_id if isinstance(cargo_id, expression.Identifier): cb_name = cargo_id.value cb_table = action3_callbacks.callbacks[feature] if cb_name in cb_table: if cb_name in seen_callbacks: raise generic.ScriptError("Callback '%s' is defined multiple times." % cb_name, cargo_id.pos) seen_callbacks.add(cb_name) info_list = cb_table[cb_name] if not isinstance(info_list, list): info_list = [info_list] for info in info_list: if info['type'] == 'cargo': # Not a callback, but an alias for a certain cargo type if info['num'] in cargo_gfx: raise generic.ScriptError("Graphics for '%s' are defined multiple times." % cb_name, cargo_id.pos) cargo_gfx[info['num']] = graphics.result elif info['type'] == 'cb': callbacks.append( (info, graphics.result) ) elif info['type'] == 'override': assert livery_override is None livery_override = graphics.result else: assert False continue # Not a callback, so it must be a 'normal' cargo (vehicles/stations only) cargo_id = cargo_id.reduce_constant(global_constants.const_list) # Raise the error only now, to let the 'unknown identifier' take precedence if feature >= 5: raise generic.ScriptError("Associating graphics with a specific cargo is possible only for vehicles and stations.", cargo_id.pos) if cargo_id.value in cargo_gfx: raise generic.ScriptError("Graphics for cargo %d are defined multiple times." % cargo_id.value, cargo_id.pos) cargo_gfx[cargo_id.value] = graphics.result if graphics_block.default_graphics is not None: if 'default' not in action3_callbacks.callbacks[feature]: raise generic.ScriptError("Default graphics may not be defined for this feature (0x%02X)." % feature, graphics_block.default_graphics.pos) if None in cargo_gfx: raise generic.ScriptError("Default graphics are defined twice.", graphics_block.default_graphics.pos) cargo_gfx[None] = graphics_block.default_graphics if len(callbacks) != 0: cb_flags = 0 # Determine the default value if None not in cargo_gfx: cargo_gfx[None] = expression.SpriteGroupRef(expression.Identifier('CB_FAILED', None), [], None) default_val = cargo_gfx[None] cb_mapping = {} cb_buy_mapping = {} # Special case for vehicle cb 36, maps var10 values to spritegroups cb36_mapping = {} cb36_buy_mapping = {} # Sspecial case for industry production CB, maps var18 values to spritegroups prod_cb_mapping = {} for cb_info, gfx in callbacks: if 'flag_bit' in cb_info: # Set a bit in the CB flags property cb_flags |= 1 << cb_info['flag_bit'] value_function = cb_info.get('value_function', None) mapping_val = (gfx, value_function) # See action3_callbacks for info on possible values purchase = cb_info.get('purchase', 0) if isinstance(purchase, str): # Not in purchase list, if separate purchase CB is set purchase = 0 if purchase in seen_callbacks else 1 # Explicit purchase CBs will need a purchase cargo, even if not needed for graphics if purchase == 2 and 0xFF not in cargo_gfx: cargo_gfx[0xFF] = default_val num = cb_info['num'] if num == 0x36: if purchase != 2: cb36_mapping[cb_info['var10']] = mapping_val if purchase != 0: cb36_buy_mapping[cb_info['var10']] = mapping_val elif feature == 0x0A and num == 0x00: # Industry production CB assert purchase == 0 prod_cb_mapping[cb_info['var18']] = mapping_val else: if purchase != 2: cb_mapping[num] = mapping_val if purchase != 0: cb_buy_mapping[num] = mapping_val if cb_flags != 0: prepend_action_list.extend(action0.get_callback_flags_actions(feature, id, cb_flags)) # Handle CB 36 if len(cb36_mapping) != 0: actions, cb36_ref = create_cb_choice_varaction2(feature, 0x10, 0xFF, cb36_mapping, default_val) prepend_action_list.extend(actions) cb_mapping[0x36] = (cb36_ref, None) if len(cb36_buy_mapping) != 0: actions, cb36_ref = create_cb_choice_varaction2(feature, 0x10, 0xFF, cb36_buy_mapping, default_val) prepend_action_list.extend(actions) cb_buy_mapping[0x36] = (cb36_ref, None) if len(prod_cb_mapping) != 0: actions, cb_ref = create_cb_choice_varaction2(feature, 0x18, 0xFF, prod_cb_mapping, default_val) prepend_action_list.extend(actions) cb_mapping[0x00] = (cb_ref, None) for cargo in sorted(cargo_gfx): mapping = cb_buy_mapping if cargo == 0xFF else cb_mapping if len(mapping) == 0: # No callbacks here, so move along continue if cargo_gfx[cargo] != default_val: # There are cargo-specific graphics, be sure to handle those # Unhandled callbacks should chain to the default, though mapping = mapping.copy() mapping[0x00] = (cargo_gfx[cargo], None) actions, cb_ref = create_cb_choice_varaction2(feature, 0x0C, 0xFFFF, mapping, default_val) prepend_action_list.extend(actions) cargo_gfx[cargo] = cb_ref # Make sure to sort to make the order well-defined offset = 7 if feature <= 3 else 5 for cargo_id in sorted(cargo_gfx): if cargo_id is None: continue result, comment = action2var.parse_result(cargo_gfx[cargo_id], action_list, act6, offset + 1, act3, None, 0x89) act3.cid_mappings.append( (cargo_id, result, comment) ) offset += 3 if None in cargo_gfx: result, comment = action2var.parse_result(cargo_gfx[None], action_list, act6, offset, act3, None, 0x89) act3.def_cid = result act3.default_comment = comment else: act3.def_cid = None act3.default_comment = '' if livery_override is not None: act6livery = action6.Action6() # Add any extra actions before the main action3 (TTDP requirement) act3livery = create_action3(feature, id, action_list, act6livery, True) offset = 7 if feature <= 3 else 5 result, comment = action2var.parse_result(livery_override, action_list, act6livery, offset, act3livery, None, 0x89) act3livery.def_cid = result act3livery.default_comment = comment if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act3) if livery_override is not None: if len(act6livery.modifications) > 0: action_list.append(act6livery) action_list.append(act3livery) action6.free_parameters.restore() return prepend_action_list + action_list nml-0.2.4/nml/actions/action10.py0000644000061700006170000000207312036626441017253 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import base_action class Action10(base_action.BaseAction): def __init__(self, label): self.label = label def write(self, file): file.start_sprite(2) file.print_bytex(0x10) file.print_bytex(self.label) file.newline() file.end_sprite() def skip_action9(self): return False def skip_needed(self): return False nml-0.2.4/nml/actions/action4.py0000644000061700006170000001672612036626442017211 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, grfstrings from nml.actions import base_action, action6, actionD class Action4(base_action.BaseAction): """ Class representing a single action 4. Format: 04 @ivar feature: Feature of this action 4 @type feature: C{int} @ivar lang: Language ID of the text (set as ##grflangid in the .lng file, 0x7F for default) @type lang: C{int} @ivar size: Size of the id, may be 1 (byte), 2 (word) or 3 (ext. byte) @type size: C{int} @ivar id: ID of the first string to write @type id: C{int} @ivar texts: List of strings to write @type texts: C{list} of C{unicode} """ def __init__(self, feature, lang, size, id, texts): self.feature = feature self.lang = lang self.size = size self.id = id self.texts = texts def prepare_output(self): #To indicate a word value, bit 7 of the lang ID must be set if self.size == 2: self.lang = self.lang | 0x80 def write(self, file): size = 4 + self.size for text in self.texts: size += grfstrings.get_string_size(text) file.start_sprite(size) file.print_bytex(4) file.print_bytex(self.feature) file.print_bytex(self.lang) file.print_bytex(len(self.texts)) file.print_varx(self.id, self.size) for text in self.texts: file.print_string(text) file.newline() file.end_sprite() def skip_action9(self): return False # List of various string ranges that may be used # Attributes: # - random_id: If true, string IDs may be allocated randomly, else the ID has a special meaning and must be assigned (e.g. for vehicles, string ID = vehicle ID) # - ids: List of free IDs, only needed if random_id is true. Whenever an ID is used, it's removed from the list string_ranges = { 0xC4: {'random_id': False}, # Station class names 0xC5: {'random_id': False}, # Station names 0xC9: {'random_id': False}, # House name 0xD0: {'random_id': True, 'ids': list(range(0x3FF, -1, -1))}, # Misc. text ids, used for callbacks and such 0xDC: {'random_id': True, 'ids': list(range(0xFF, -1, -1))}, # Misc. persistent text ids, used to set properties } # Mapping of string identifiers to D0xx/DCxx text ids # This allows outputting strings only once, instead of everywhere they are used used_strings = { 0xD0: {}, 0xDC: {}, } def get_global_string_actions(): """ Get a list of global string actions i.e. print all D0xx / DCxx texts at once @return: A list of all D0xx / DCxx action4s @rtype: C{list} of L{BaseAction} """ texts = [] actions = [] for string_range, strings in used_strings.iteritems(): for feature_name, id in strings.iteritems(): feature, string_name = feature_name texts.append( (0x7F, (string_range << 8) | id, grfstrings.get_translation(string_name), feature) ) for lang_id in grfstrings.get_translations(string_name): texts.append( (lang_id, (string_range << 8) | id, grfstrings.get_translation(string_name, lang_id), feature) ) last_lang = -1 last_id = -1 last_feature = -1 # Sort to have a deterministic ordering and to have as much consecutive IDs as possible texts.sort(key=lambda text: (-1 if text[0] == 0x7F else text[0], text[1])) for text in texts: str_lang, str_id, str_text, feature = text # If possible, append strings to the last action 4 instead of creating a new one if str_lang != last_lang or str_id - 1 != last_id or feature != last_feature or len(actions[-1].texts) == 0xFF: actions.append(Action4(feature, str_lang, 2, str_id, [str_text])) else: actions[-1].texts.append(str_text) last_lang = str_lang last_id = str_id last_feature = feature return actions def get_string_action4s(feature, string_range, string, id = None): """ Let a string from the lang files be used in the rest of NML. This may involve adding actions directly, but otherwise an ID is allocated and the string will be written later @param feature: Feature that uses the string @type feature: C{int} @param string_range: String range to use, either a value from L{string_ranges} or C{None} if N/A (item names) @type string_range: C{int} or C{None} @param string: String to parse @type string: L{expression.String} @param id: ID to use for this string, or C{None} if it will be allocated dynamically (random_id is true for the string range) @type id: L{Expression} or C{None} @return: A tuple of two values: - ID of the string (useful if allocated dynamically) - Resulting action list to be appended @rtype: C{tuple} of (C{int}, C{list} of L{BaseAction}) """ grfstrings.validate_string(string) write_action4s = True action6.free_parameters.save() actions = [] mod = None if string_range is not None: size = 2 if string_ranges[string_range]['random_id']: # ID is allocated randomly, we will output the actions later write_action4s = False if (feature, string) in used_strings[string_range]: id_val = used_strings[string_range][(feature, string)] else: id_val = string_ranges[string_range]['ids'].pop() used_strings[string_range][(feature, string)] = id_val else: # ID must be supplied assert id is not None assert isinstance(id, expression.ConstantNumeric) id_val = id.value id_val = id_val | (string_range << 8) else: # Not a string range, so we must have an idea assert id is not None size = 3 if feature <= 3 else 1 if isinstance(id, expression.ConstantNumeric): id_val = id.value else: id_val = 0 tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) actions.extend(tmp_param_actions) # Apply ID via action4 later mod = (tmp_param, 2 if feature <= 3 else 1, 5 if feature <= 3 else 4) if write_action4s: strings = [(lang_id, grfstrings.get_translation(string, lang_id)) for lang_id in grfstrings.get_translations(string)] # Sort the strings for deterministic ordering and prepend the default language strings = [(0x7F, grfstrings.get_translation(string))] + sorted(strings, key=lambda lang_text: lang_text[0]) for lang_id, text in strings: if mod is not None: act6 = action6.Action6() act6.modify_bytes(*mod) actions.append(act6) actions.append(Action4(feature, lang_id, size, id_val, [text])) action6.free_parameters.restore() return (id_val, actions) nml-0.2.4/nml/actions/action6.py0000644000061700006170000000300312036626442017173 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import free_number_list from nml.actions import base_action free_parameters = free_number_list.FreeNumberList(list(range(0x7F, 0x3F, -1))) class Action6(base_action.BaseAction): def __init__(self): self.modifications = [] def modify_bytes(self, param, num_bytes, offset): self.modifications.append( (param, num_bytes, offset) ) def write(self, file): size = 2 + 5 * len(self.modifications) file.start_sprite(size) file.print_bytex(6) file.newline() for mod in self.modifications: file.print_bytex(mod[0]) file.print_bytex(mod[1]) file.print_bytex(0xFF) file.print_wordx(mod[2]) file.newline() file.print_bytex(0xFF) file.newline() file.end_sprite() def skip_action7(self): return False nml-0.2.4/nml/actions/action2layout.py0000644000061700006170000005376112036626442020445 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression, nmlop from nml.actions import action2, action6, actionD, action1, action2var, real_sprite from nml.ast import general class Action2Layout(action2.Action2): def __init__(self, feature, name, ground_sprite, sprite_list, param_registers): action2.Action2.__init__(self, feature, name) assert ground_sprite.type == Action2LayoutSpriteType.GROUND self.ground_sprite = ground_sprite self.sprite_list = sprite_list self.param_registers = param_registers def resolve_tmp_storage(self): for reg in self.param_registers: location = self.tmp_locations[0] self.remove_tmp_location(location, False) reg.set_register(location) def write(self, file): advanced = any(x.is_advanced_sprite() for x in self.sprite_list + [self.ground_sprite]) size = 5 if advanced: size += self.ground_sprite.get_registers_size() for sprite in self.sprite_list: if sprite.type == Action2LayoutSpriteType.CHILD: size += 7 else: size += 10 if advanced: size += sprite.get_registers_size() if len(self.sprite_list) == 0: size += 9 action2.Action2.write_sprite_start(self, file, size) if advanced: file.print_byte(0x40 | len(self.sprite_list)) else: file.print_byte(len(self.sprite_list)) self.ground_sprite.write_sprite_number(file) if advanced: self.ground_sprite.write_flags(file) self.ground_sprite.write_registers(file) file.newline() if len(self.sprite_list) == 0: file.print_dwordx(0) #sprite number 0 == no sprite for i in range(0, 5): file.print_byte(0) #empty bounding box. Note that number of zeros is 5, not 6 else: for sprite in self.sprite_list: sprite.write_sprite_number(file) if advanced: sprite.write_flags(file) file.print_byte(sprite.get_param('xoffset').value) file.print_byte(sprite.get_param('yoffset').value) if sprite.type == Action2LayoutSpriteType.CHILD: file.print_bytex(0x80) else: #normal building sprite file.print_byte(sprite.get_param('zoffset').value) file.print_byte(sprite.get_param('xextent').value) file.print_byte(sprite.get_param('yextent').value) file.print_byte(sprite.get_param('zextent').value) if advanced: sprite.write_registers(file) file.newline() file.end_sprite() class Action2LayoutSpriteType(object): GROUND = 0 BUILDING = 1 CHILD = 2 #these keywords are used to identify a ground/building/childsprite layout_sprite_types = { 'ground' : Action2LayoutSpriteType.GROUND, 'building' : Action2LayoutSpriteType.BUILDING, 'childsprite' : Action2LayoutSpriteType.CHILD, } class Action2LayoutSprite(object): def __init__(self, feature, type, pos = None, extra_dicts = []): self.feature = feature self.type = type self.pos = pos self.extra_dicts = extra_dicts self.params = { 'sprite' : {'value': None, 'validator': self._validate_sprite}, 'recolour_mode' : {'value': 0, 'validator': self._validate_recolour_mode}, 'palette' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_palette}, 'always_draw' : {'value': 0, 'validator': self._validate_always_draw}, 'xoffset' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_bounding_box}, 'yoffset' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_bounding_box}, 'zoffset' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_bounding_box}, 'xextent' : {'value': expression.ConstantNumeric(16), 'validator': self._validate_bounding_box}, 'yextent' : {'value': expression.ConstantNumeric(16), 'validator': self._validate_bounding_box}, 'zextent' : {'value': expression.ConstantNumeric(16), 'validator': self._validate_bounding_box}, 'hide_sprite' : {'value': None, 'validator': self._validate_hide_sprite}, # Value not used } for i in self.params: self.params[i]['is_set'] = False self.params[i]['register'] = None self.sprite_from_action1 = False self.palette_from_action1 = False def is_advanced_sprite(self): if self.palette_from_action1: return True return len(self.get_all_registers()) != 0 def get_registers_size(self): # Number of registers to write size = len(self.get_all_registers()) # Add 2 for the flags size += 2 return size def write_flags(self, file): flags = 0 if self.get_register('hide_sprite') is not None: flags |= 1 << 0 if self.get_register('sprite') is not None: flags |= 1 << 1 if self.get_register('palette') is not None: flags |= 1 << 2 if self.palette_from_action1: flags |= 1 << 3 # for building sprites: bit 4 => xoffset+yoffset, bit 5 => zoffset (x and y always set totgether) # for child sprites: bit 4 => xoffset, bit 5 => yoffset if self.type == Action2LayoutSpriteType.BUILDING: assert (self.get_register('xoffset') is not None) == (self.get_register('yoffset') is not None) if self.get_register('xoffset') is not None: flags |= 1 << 4 nextreg = 'zoffset' if self.type == Action2LayoutSpriteType.BUILDING else 'yoffset' if self.get_register(nextreg) is not None: flags |= 1 << 5 file.print_wordx(flags) def write_register(self, file, name): register = self.get_register(name)[0] file.print_bytex(register.parameter) def write_registers(self, file): if self.is_set('hide_sprite'): self.write_register(file, 'hide_sprite') if self.get_register('sprite') is not None: self.write_register(file, 'sprite') if self.get_register('palette') is not None: self.write_register(file, 'palette') if self.get_register('xoffset') is not None: self.write_register(file, 'xoffset') if self.get_register('yoffset') is not None: self.write_register(file, 'yoffset') if self.get_register('zoffset') is not None: self.write_register(file, 'zoffset') def write_sprite_number(self, file): num = self.get_sprite_number() if isinstance(num, expression.ConstantNumeric): num.write(file, 4) else: file.print_dwordx(0) def get_sprite_number(self): # Layout of sprite number # bit 0 - 13: Sprite number # bit 14 - 15: Recolour mode (normal/transparent/remap) # bit 16 - 29: Palette sprite number # bit 30: Always draw sprite, even in transparent mode # bit 31: This is a custom sprite (from action1), not a TTD sprite if not self.is_set('sprite'): raise generic.ScriptError("'sprite' must be set for this layout sprite", self.pos) # Make sure that recolouring is set correctly if self.get_param('recolour_mode') == 0 and self.is_set('palette'): raise generic.ScriptError("'palette' may not be set when 'recolour_mode' is RECOLOUR_NONE.") elif self.get_param('recolour_mode') != 0 and not self.is_set('palette'): raise generic.ScriptError("'palette' must be set when 'recolour_mode' is not set to RECOLOUR_NONE.") # add the constant terms first sprite_num = self.get_param('recolour_mode') << 14 if self.get_param('always_draw'): sprite_num |= 1 << 30 if self.sprite_from_action1: sprite_num |= 1 << 31 add_sprite = False sprite = self.get_param('sprite') if isinstance(sprite, expression.ConstantNumeric): sprite_num |= sprite.value else: add_sprite = True add_palette = False palette = self.get_param('palette') if isinstance(palette, expression.ConstantNumeric): sprite_num |= palette.value << 16 else: add_palette = True expr = expression.ConstantNumeric(sprite_num, sprite.pos) if add_sprite: expr = expression.BinOp(nmlop.ADD, sprite, expr, sprite.pos) if add_palette: expr = expression.BinOp(nmlop.ADD, palette, expr, sprite.pos) return expr.reduce() def get_param(self, name): assert name in self.params return self.params[name]['value'] def is_set(self, name): assert name in self.params return self.params[name]['is_set'] def get_register(self, name): assert name in self.params return self.params[name]['register'] def get_all_registers(self): return [self.get_register(name) for name in sorted(self.params) if self.get_register(name) is not None] def create_register(self, name, value): if isinstance(value, expression.StorageOp) and value.name == "LOAD_TEMP" and isinstance(value.register, expression.ConstantNumeric): store_tmp = None load_tmp = action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, value.register.value) else: store_tmp = action2var.VarAction2StoreTempVar() load_tmp = action2var.VarAction2LoadTempVar(store_tmp) self.params[name]['register'] = (load_tmp, store_tmp, value) def set_param(self, name, value): assert isinstance(name, expression.Identifier) assert isinstance(value, expression.Expression) name = name.value if not name in self.params: raise generic.ScriptError("Unknown sprite parameter '%s'" % name, value.pos) if self.is_set(name): raise generic.ScriptError("Sprite parameter '%s' can be set only once per sprite." % name, value.pos) self.params[name]['value'] = self.params[name]['validator'](name, value) self.params[name]['is_set'] = True def resolve_spritegroup_ref(self, sg_ref): """ Resolve a reference to a (sprite/palette) sprite group @param sg_ref: Reference to a sprite group @type sg_ref: L{SpriteGroupRef} @return: Sprite number (index of action1 set) to use @rtype: L{Expression} """ spriteset = action2.resolve_spritegroup(sg_ref.name) if len(sg_ref.param_list) == 0: offset = None elif len(sg_ref.param_list) == 1: id_dicts = [(spriteset.labels, lambda val, pos: expression.ConstantNumeric(val, pos))] offset = action2var.reduce_varaction2_expr(sg_ref.param_list[0], self.feature, self.extra_dicts + id_dicts) if isinstance(offset, expression.ConstantNumeric): generic.check_range(offset.value, 0, len(real_sprite.parse_sprite_list(spriteset.sprite_list, spriteset.pcx)) - 1, "offset within spriteset", sg_ref.pos) else: raise generic.ScriptError("Expected 0 or 1 parameter, got " + str(len(sg_ref.param_list)), sg_ref.pos) num = action1.get_action1_index(spriteset) generic.check_range(num, 0, (1 << 14) - 1, "sprite", sg_ref.pos) return expression.ConstantNumeric(num), offset def _validate_sprite(self, name, value): if isinstance(value, expression.SpriteGroupRef): self.sprite_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val else: self.sprite_from_action1 = False if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "sprite", value.pos) return value self.create_register(name, value) return expression.ConstantNumeric(0) def _validate_recolour_mode(self, name, value): if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Expected a compile-time constant.", value.pos) if not value.value in (0, 1, 2): raise generic.ScriptError("Value of 'recolour_mode' must be RECOLOUR_NONE, RECOLOUR_TRANSPARENT or RECOLOUR_REMAP.") return value.value def _validate_palette(self, name, value): if isinstance(value, expression.SpriteGroupRef): self.palette_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val else: if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "palette", value.pos) self.palette_from_action1 = False return value def _validate_always_draw(self, name, value): if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Expected a compile-time constant number.", value.pos) # Not valid for ground sprites, raise error if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError("'always_draw' may not be set for groundsprites, these are always drawn anyways.", value.pos) if value.value not in (0, 1): raise generic.ScriptError("Value of 'always_draw' should be 0 or 1", value.pos) return value.value def _validate_bounding_box(self, name, value): if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError(name + " can not be set for ground sprites", value.pos) elif self.type == Action2LayoutSpriteType.CHILD: if name not in ('xoffset', 'yoffset'): raise generic.ScriptError(name + " can not be set for child sprites", value.pos) if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, 255, name, value.pos) return value else: assert self.type == Action2LayoutSpriteType.BUILDING if name in ('xoffset', 'yoffset', 'zoffset'): if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, -128, 127, name, value.pos) return value else: assert name in ('xextent', 'yextent', 'zextent') if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Value of '%s' must be a compile-time constant number." % name, value.pos) generic.check_range(value.value, 0, 255, name, value.pos) return value # Value must be written to a register self.create_register(name, value) if self.type == Action2LayoutSpriteType.BUILDING: # For building sprites, x and y registers are always written together if name == 'xoffset' and self.get_register('yoffset') is None: self.create_register('yoffset', expression.ConstantNumeric(0)) if name == 'yoffset' and self.get_register('xoffset') is None: self.create_register('xoffset', expression.ConstantNumeric(0)) return expression.ConstantNumeric(0) def _validate_hide_sprite(self, name, value): self.create_register(name, expression.Not(value)) return None def get_layout_action2s(spritelayout, feature): ground_sprite = None building_sprites = [] actions = [] if feature not in action2.features_sprite_layout: raise generic.ScriptError("Sprite layouts are not supported for feature '%s'." % general.feature_name(feature)) # Allocate registers param_map = {} param_registers = [] for i, param in enumerate(spritelayout.param_list): reg = action2var.VarAction2LayoutParam() param_registers.append(reg) param_map[param.value] = reg param_map = (param_map, lambda value, pos: action2var.VarAction2LoadLayoutParam(value)) spritelayout.register_map[feature] = param_registers # Reduce all expresssions, can't do that earlier as feature is not known all_sprite_sets = [] layout_sprite_list = [] # Create a new structure for layout_sprite in spritelayout.layout_sprite_list: param_list = [] layout_sprite_list.append( (layout_sprite.type, layout_sprite.pos, param_list) ) for param in layout_sprite.param_list: param_val = action2var.reduce_varaction2_expr(param.value, feature, [param_map]) param_list.append( (param.name, param_val) ) if isinstance(param_val, expression.SpriteGroupRef): spriteset = action2.resolve_spritegroup(param_val.name) if not spriteset.is_spriteset(): raise generic.ScriptError("Expected a reference to a spriteset.", param_val.pos) all_sprite_sets.append(spriteset) actions.extend(action1.add_to_action1(all_sprite_sets, feature, spritelayout.pos)) temp_registers = [] for type, pos, param_list in layout_sprite_list: if type.value not in layout_sprite_types: raise generic.ScriptError("Invalid sprite type '%s' encountered. Expected 'ground', 'building', or 'childsprite'." % type.value, type.pos) sprite = Action2LayoutSprite(feature, layout_sprite_types[type.value], pos, [param_map]) for name, value in param_list: sprite.set_param(name, value) temp_registers.extend(sprite.get_all_registers()) if sprite.type == Action2LayoutSpriteType.GROUND: if ground_sprite is not None: raise generic.ScriptError("Sprite layout can have no more than one ground sprite", spritelayout.pos) ground_sprite = sprite else: building_sprites.append(sprite) if ground_sprite is None: if len(building_sprites) == 0: #no sprites defined at all, that's not very much. raise generic.ScriptError("Sprite layout requires at least one sprite", spritegroup.pos) #set to 0 for no ground sprite ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND) ground_sprite.set_param(expression.Identifier('sprite'), expression.ConstantNumeric(0)) action6.free_parameters.save() act6 = action6.Action6() offset = 4 sprite_num = ground_sprite.get_sprite_number() if not isinstance(sprite_num, expression.ConstantNumeric): param, extra_actions = actionD.get_tmp_parameter(sprite_num) actions.extend(extra_actions) act6.modify_bytes(param, 4, offset) offset += 4 offset += ground_sprite.get_registers_size() for sprite in building_sprites: sprite_num = sprite.get_sprite_number() if not isinstance(sprite_num, expression.ConstantNumeric): param, extra_actions = actionD.get_tmp_parameter(sprite_num) actions.extend(extra_actions) act6.modify_bytes(param, 4, offset) offset += sprite.get_registers_size() offset += 7 if sprite.type == Action2LayoutSpriteType.CHILD else 10 if len(act6.modifications) > 0: actions.append(act6) layout_action = Action2Layout(feature, spritelayout.name.value + (" - feature %02X" % feature), ground_sprite, building_sprites, param_registers) actions.append(layout_action) if temp_registers: varact2parser = action2var.Varaction2Parser(feature) for register_info in temp_registers: reg, expr = register_info[1], register_info[2] if reg is None: continue varact2parser.parse_expr(expr) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(reg) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += reg.get_size() + 2 # Only continue if we actually needed any new registers if temp_registers and varact2parser.var_list: #Remove the last VAL2 operator varact2parser.var_list.pop() varact2parser.var_list_size -= 1 actions.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: actions.append(extra_act6) varaction2 = action2var.Action2Var(feature, "%s@registers - feature %02X" % (spritelayout.name.value, feature), 0x89) varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(spritelayout.name, [], None, layout_action) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) # Make sure that registers allocated here are not used by the spritelayout action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) spritelayout.set_action2(varaction2, feature) actions.append(varaction2) else: spritelayout.set_action2(layout_action, feature) action6.free_parameters.restore() return actions nml-0.2.4/nml/actions/action2random.py0000644000061700006170000003410012036626442020372 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action2, action2var, action2real, action6 from nml import generic, expression, global_constants, nmlop class Action2Random(action2.Action2): def __init__(self, feature, name, type_byte, count, triggers, randbit, nrand): action2.Action2.__init__(self, feature, name) self.type_byte = type_byte self.count = count self.triggers = triggers self.randbit = randbit self.nrand = nrand self.choices = [] def prepare_output(self): action2.Action2.prepare_output(self) for choice in self.choices: if isinstance(choice.result, expression.SpriteGroupRef): choice.result = choice.result.get_action2_id(self.feature) else: choice.result = choice.result.value | 0x8000 def write(self, file): # [] size = 4 + 2 * self.nrand + (self.count is not None) action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.type_byte) if self.count is not None: file.print_bytex(self.count) file.print_bytex(self.triggers) file.print_byte(self.randbit) file.print_bytex(self.nrand) file.newline() for choice in self.choices: for i in range(0, choice.prob): file.print_wordx(choice.result) file.comment(choice.comment) file.end_sprite() class RandomAction2Choice(object): def __init__(self, result, prob, comment): self.result = result self.prob = prob self.comment = comment vehicle_random_types = { 'SELF' : {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': True}, 'PARENT' : {'type': 0x83, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}, 'TILE' : {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}, 'BACKWARD_SELF' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0x00}, 'FORWARD_SELF' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0x40}, 'BACKWARD_ENGINE' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0x80}, 'BACKWARD_SAMEID' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0xC0}, } random_types = { 0x00 : vehicle_random_types, 0x01 : vehicle_random_types, 0x02 : vehicle_random_types, 0x03 : vehicle_random_types, 0x04 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 16, 'triggers': True}, 'TILE': {'type': 0x80, 'param': 0, 'first_bit': 16, 'num_bits': 4, 'triggers': True}}, 0x05 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}}, 0x06 : {}, 0x07 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': True}}, 0x08 : {}, 0x09 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': True}, 'PARENT': {'type': 0x83, 'first_bit': 0, 'num_bits': 16, 'triggers': True}}, 0x0A : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 16, 'triggers': False}}, 0x0B : {}, 0x0C : {}, 0x0D : {}, 0x0E : {}, 0x0F : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}}, 0x10 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 2, 'triggers': False}}, 0x11 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 16, 'triggers': False}, 'TILE': {'type': 0x80, 'first_bit': 16, 'num_bits': 4, 'triggers': False}}, } def parse_randomswitch_type(random_switch): """ Parse the type of a random switch to determine the type and random bits to use. @param random_switch: Random switch to parse the type of @type random_switch: L{RandomSwitch} @return: A tuple containing the following: - The type byte of the resulting random action2. - The value to use as , None if N/A. - Expression to parse in a preceding switch-block, None if N/A. - The first random bit that should be used (often 0) - The number of random bits available @rtype: C{tuple} of (C{int}, C{int} or C{None}, L{Expression} or C{None}, C{int}, C{int}) """ # Extract some stuff we'll often need type_str = random_switch.type.value type_pos = random_switch.type.pos feature_val = random_switch.feature_set.copy().pop() # Validate type name / param combination if type_str not in random_types[feature_val]: raise generic.ScriptError("Invalid combination for random_switch feature %d and type '%s'. " % (feature_val, type_str), type_pos) type_info = random_types[feature_val][type_str] count_expr = None if random_switch.type_count is None: # No param given if type_info['param'] == 1: raise generic.ScriptError("Value '%s' for random_switch parameter 2 'type' requires a parameter." % type_str, type_pos) count = None else: # Param given if type_info['param'] == 0: raise generic.ScriptError("Value '%s' for random_switch parameter 2 'type' should not have a parameter." % type_str, type_pos) if isinstance(random_switch.type_count, expression.ConstantNumeric) and 1 <= random_switch.type_count.value <= 15: count = random_switch.type_count.value else: count = 0 count_expr = expression.BinOp(nmlop.STO_TMP, random_switch.type_count, expression.ConstantNumeric(0x100), type_pos) count = type_info['value'] | count if random_switch.triggers.value != 0 and not type_info['triggers']: raise generic.ScriptError("Triggers may not be set for random_switch feature %d and type '%s'. " % (feature_val, type_str), type_pos) # Determine type byte and random bits type_byte = type_info['type'] start_bit = type_info['first_bit'] bits_available = type_info['num_bits'] return type_byte, count, count_expr, start_bit, bits_available def lookup_random_action2(sg_ref): """ Lookup a sprite group reference to find the corresponding random action2 @param sg_ref: Reference to random action2 @type sg_ref: L{SpriteGroupRef} @return: Random action2 corresponding to this sprite group, or None if N/A @rtype: L{Action2Random} or C{None} """ spritegroup = action2.resolve_spritegroup(sg_ref.name) act2 = spritegroup.random_act2 assert act2 is None or isinstance(act2, Action2Random) return act2 def parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand): """ Handle the dependencies between random chains to determine the random bits to use @param random_switch: Random switch to parse @type random_switch: L{RandomSwitch} @param start_bit: First available random bit @type start_bit: C{int} @param bits_available: Number of random bits available @type bits_available: C{int} @param nrand: Number of random choices to use @type nrand: C{int} @return: A tuple of two values: - The first random bit to use - The number of random choices to use. This may be higher the the original amount passed as paramter @rtype: C{tuple} of (C{int}, C{int}) """ #Dependent random chains act2_to_copy = None for dep in random_switch.dependent: act2 = lookup_random_action2(dep) if act2 is None: continue # May happen if said random switch is not used and therefore not parsed if act2_to_copy is not None: if act2_to_copy.randbit != act2.randbit: raise generic.ScriptError("random_switch '%s' cannot be dependent on both '%s' and '%s' as these are independent of eachother." % (random_switch.name.value, act2_to_copy.name, act2.name), random_switch.pos) if act2_to_copy.nrand != act2.nrand: raise generic.ScriptError("random_switch '%s' cannot be dependent on both '%s' and '%s' as they don't use the same amount of random data." % (random_switch.name.value, act2_to_copy.name, act2.name), random_switch.pos) else: act2_to_copy = act2 if act2_to_copy is not None: randbit = act2_to_copy.randbit if nrand > act2_to_copy.nrand: raise generic.ScriptError("random_switch '%s' cannot be dependent on '%s' as it requires more random data." % (random_switch.name.value, act2_to_copy.name), random_switch.pos) nrand = act2_to_copy.nrand else: randbit = -1 #INdependent random chains possible_mask = ((1 << bits_available) - 1) << start_bit for indep in random_switch.independent: act2 = lookup_random_action2(indep) if act2 is None: continue # May happen if said random switch is not used and therefore not parsed possible_mask &= ~((act2.nrand - 1) << act2.randbit) required_mask = nrand - 1 if randbit != -1: #randbit has already been determined. Check that it is suitable if possible_mask & (required_mask << randbit) != (required_mask << randbit): raise generic.ScriptError("Combination of dependence on and independence from random_switches is not possible for random_switch '%s'." % random_switch.name.value, random_switch.pos) else: #find a suitable randbit for i in range(start_bit, bits_available + start_bit): if possible_mask & (required_mask << i) == (required_mask << i): randbit = i break else: raise generic.ScriptError("Independence of all given random_switches is not possible for random_switch '%s'." % random_switch.name.value, random_switch.pos) return randbit, nrand def parse_randomswitch(random_switch): """ Parse a randomswitch block into actions @param random_switch: RandomSwitch block to parse @type random_switch: L{RandomSwitch} @return: List of actions @rtype: C{list} of L{BaseAction} """ action_list = action2real.create_spriteset_actions(random_switch) feature = random_switch.feature_set.copy().pop() type_byte, count, count_expr, start_bit, bits_available = parse_randomswitch_type(random_switch) total_prob = sum([choice.probability.value for choice in random_switch.choices]) assert total_prob > 0 nrand = 1 while nrand < total_prob: nrand <<= 1 # Verify that enough random data is available if min(1 << bits_available, 0x80) < nrand: raise generic.ScriptError("The maximum sum of all random_switch probabilities is %d, encountered %d." % (min(1 << bits_available, 0x80), total_prob), random_switch.pos) randbit, nrand = parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand) random_action2 = Action2Random(feature, random_switch.name.value, type_byte, count, random_switch.triggers.value, randbit, nrand) random_switch.random_act2 = random_action2 action6.free_parameters.save() act6 = action6.Action6() offset = 8 #divide the 'extra' probabilities in an even manner i = 0 resulting_prob = dict(zip(random_switch.choices, [c.probability.value for c in random_switch.choices])) while i < (nrand - total_prob): best_choice = None best_ratio = 0 for choice in random_switch.choices: #float division, so 9 / 10 = 0.9 ratio = choice.probability.value / float(resulting_prob[choice] + 1) if ratio > best_ratio: best_ratio = ratio best_choice = choice assert best_choice is not None resulting_prob[best_choice] += 1 i += 1 for choice in random_switch.choices: res_prob = resulting_prob[choice] result, comment = action2var.parse_result(choice.result, action_list, act6, offset, random_action2, None, 0x89, res_prob) offset += res_prob * 2 comment = "(%d/%d) -> (%d/%d): " % (choice.probability.value, total_prob, res_prob, nrand) + comment random_action2.choices.append(RandomAction2Choice(result, res_prob, comment)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(random_action2) if count_expr is None: random_switch.set_action2(random_action2, feature) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var(feature, '%s@registers' % random_switch.name.value, 0x89) varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(count_expr) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(random_switch.name, [], None, random_action2) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) random_switch.set_action2(varaction2, feature) action_list.append(varaction2) action6.free_parameters.restore() return action_list nml-0.2.4/nml/main.py0000644000061700006170000003611612036626442015127 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import sys, os, codecs, optparse from nml import generic, grfstrings, parser, version_info, output_base, output_nml, output_nfo, output_grf, output_dep, palette from nml.actions import action2layout, action2var, action8, sprite_count, real_sprite, action4, action0, action1, action11 from nml.ast import general, grf, alt_sprites try: import Image except ImportError: pass developmode = False # Give 'nice' error message instead of a stack dump. version = version_info.get_nml_version() def parse_cli(argv): """ Parse the command line, and process options. @return: Options, and input filename (if not supplied, use C{sys.stdin}). @rtype: C{tuple} (C{Object}, C{str} or C{None}) """ usage = "Usage: %prog [options] \n" \ "Where is the nml file to parse" opt_parser = optparse.OptionParser(usage=usage, version=version_info.get_cli_version()) opt_parser.set_defaults(debug=False, crop=False, compress=True, outputs=[], start_sprite_num=0, custom_tags="custom_tags.txt", lang_dir="lang", sprites_dir="sprites", default_lang="english.lng", forced_palette="ANY") opt_parser.add_option("-d", "--debug", action="store_true", dest="debug", help="write the AST to stdout") opt_parser.add_option("-s", "--stack", action="store_true", dest="stack", help="Dump stack when an error occurs") opt_parser.add_option("--grf", dest="grf_filename", metavar="", help="write the resulting grf to ") opt_parser.add_option("--nfo", dest="nfo_filename", metavar="", help="write nfo output to ") opt_parser.add_option("-M", action="store_true", dest="dep_check", help="output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or --grf)") opt_parser.add_option("--MF", dest="dep_filename", metavar="", help="When used with -M, specifies a file to write the dependencies to") opt_parser.add_option("--MT", dest="depgrf_filename", metavar="", help="target of the rule emitted by dependency generation (requires -M)") opt_parser.add_option("-c", action="store_true", dest="crop", help="crop extraneous transparent blue from real sprites") opt_parser.add_option("-u", action="store_false", dest="compress", help="save uncompressed data in the grf file") opt_parser.add_option("--nml", dest="nml_filename", metavar="", help="write optimized nml to ") opt_parser.add_option("-o", "--output", dest="outputs", action="append", metavar="", help="write output(nfo/grf) to ") opt_parser.add_option("-t", "--custom-tags", dest="custom_tags", metavar="", help="Load custom tags from [default: %default]") opt_parser.add_option("-l", "--lang-dir", dest="lang_dir", metavar="", help="Load language files from directory [default: %default]") opt_parser.add_option("-a", "--sprites-dir", dest="sprites_dir", metavar="", help="Store 32bpp sprites in directory [default: %default]") opt_parser.add_option("--default-lang", dest="default_lang", metavar="", help="The default language is stored in [default: %default]") opt_parser.add_option("--start-sprite", action="store", type="int", dest="start_sprite_num", metavar="", help="Set the first sprite number to write (do not use except when you output nfo that you want to include in other files)") opt_parser.add_option("-p", "--palette", dest="forced_palette", metavar="", choices = ["DOS", "WIN", "ANY"], help="Force nml to use the palette [default: %default]. Valid values are 'DOS', 'WIN', 'ANY'") opts, args = opt_parser.parse_args(argv) opts.outputfile_given = (opts.grf_filename or opts.nfo_filename or opts.nml_filename or opts.dep_filename or opts.outputs) if not args: if not opts.outputfile_given: opt_parser.print_help() sys.exit(2) input_filename = None # Output filenames, but no input -> use stdin. elif len(args) > 1: opt_parser.error("Error: only a single nml file can be read per run") else: input_filename = args[0] if not os.access(input_filename, os.R_OK): raise generic.ScriptError('Input file "%s" does not exist' % input_filename) return opts, input_filename def main(argv): global developmode opts, input_filename = parse_cli(argv) if opts.stack: developmode = True grfstrings.read_extra_commands(opts.custom_tags) grfstrings.read_lang_files(opts.lang_dir, opts.default_lang) # We have to do the dependency check first or we might later have # more targets than we asked for outputs = [] if opts.dep_check: # First make sure we have a file to output the dependencies to: dep_filename = opts.dep_filename if dep_filename is None and opts.grf_filename is not None: dep_filename = filename_output_from_input(opts.grf_filename, ".dep") if dep_filename is None and input_filename is not None: dep_filename = filename_output_from_input(input_filename, ".dep") if dep_filename is None: raise generic.ScriptError("-M requires a dependency file either via -MF, an input filename or a valid output via --grf") # Now make sure we have a file which is the target for the dependencies: depgrf_filename = opts.depgrf_filename if depgrf_filename is None and opts.grf_filename is not None: depgrf_filename = opts.grf_filename if depgrf_filename is None and input_filename is not None: depgrf_filename = filename_output_from_input(input_filename, ".grf") if depgrf_filename is None: raise generic.ScriptError("-M requires either a target grf file via -MT, an input filename or a valid output via --grf") # Only append the dependency check to the output targets when we have both, # a target grf and a file to write to if dep_filename is not None and depgrf_filename is not None: outputs.append(output_dep.OutputDEP(dep_filename, depgrf_filename)) if input_filename is None: input = sys.stdin else: input = codecs.open(input_filename, 'r', 'utf-8') # Only append an output grf name, if no ouput is given, also not implicitly via -M if not opts.outputfile_given and not outputs: opts.grf_filename = filename_output_from_input(input_filename, ".grf") if opts.grf_filename: outputs.append(output_grf.OutputGRF(opts.grf_filename, opts.compress, opts.crop)) if opts.nfo_filename: outputs.append(output_nfo.OutputNFO(opts.nfo_filename, opts.start_sprite_num)) if opts.nml_filename: outputs.append(output_nml.OutputNML(opts.nml_filename)) for output in opts.outputs: outroot, outext = os.path.splitext(output) outext = outext.lower() if outext == '.grf': outputs.append(output_grf.OutputGRF(output, opts.compress, opts.crop)) elif outext == '.nfo': outputs.append(output_nfo.OutputNFO(output, opts.start_sprite_num)) elif outext == '.nml': outputs.append(output_nml.OutputNML(output)) elif outext == '.dep': outputs.append(output_dep.OutputDEP(output, opts.grf_filename)) else: generic.print_warning("Unknown output format %s" % outext) sys.exit(2) ret = nml(input, opts.debug, outputs, opts.sprites_dir, opts.start_sprite_num, opts.forced_palette) input.close() sys.exit(ret) def filename_output_from_input(name, ext): return os.path.splitext(name)[0] + ext def nml(inputfile, output_debug, outputfiles, sprites_dir, start_sprite_num, forced_palette): generic.OnlyOnce.clear() script = inputfile.read() # Strip a possible BOM script = script.lstrip(unicode(codecs.BOM_UTF8, "utf-8")) if script.strip() == "": generic.print_warning("Empty input file") return 4 nml_parser = parser.NMLParser() result = nml_parser.parse(script) result.validate([]) if output_debug > 0: result.debug_print(0) for outputfile in outputfiles: if isinstance(outputfile, output_nml.OutputNML): outputfile.open() outputfile.write(str(result)) outputfile.close() result.register_names() result.pre_process() tmp_actions = result.get_action_list() actions = [] for action in tmp_actions: if isinstance(action, action1.SpritesetCollection): actions.extend(action.get_action_list()) else: actions.append(action) actions.extend(action11.get_sound_actions()) action8_index = -1 for i in range(len(actions) - 1, -1, -1): if isinstance(actions[i], (action2var.Action2Var, action2layout.Action2Layout)): actions[i].resolve_tmp_storage() elif isinstance(actions[i], action8.Action8): action8_index = i if action8_index != -1: lang_actions = [] # Add plural/gender/case tables for lang_pair in grfstrings.langs: lang_id, lang = lang_pair lang_actions.extend(action0.get_language_translation_tables(lang)) # Add global strings lang_actions.extend(action4.get_global_string_actions()) actions = actions[:action8_index + 1] + lang_actions + actions[action8_index + 1:] sprite_files = set() for action in actions: if isinstance(action, real_sprite.RealSpriteAction) and not isinstance(action, real_sprite.RecolourSpriteAction): if action.sprite.is_empty: continue action.sprite.validate_size() sprite_files.add(action.sprite.file.value) # Check whether we can terminate sprite processing prematurely for # dependency checks skip_sprite_processing = True for outputfile in outputfiles: if isinstance(outputfile, output_dep.OutputDEP): outputfile.open() for f in sprite_files: outputfile.write(f) outputfile.close() skip_sprite_processing &= outputfile.skip_sprite_checks() if skip_sprite_processing: return 0 if not Image and len(sprite_files) > 0: generic.print_warning("PIL (python-imaging) wasn't found, no support for using graphics") sys.exit(3) used_palette = forced_palette last_file = None for f in sprite_files: if not os.path.exists(f): raise generic.ImageError("File doesn't exist", f) try: im = Image.open(f) except IOError, ex: raise generic.ImageError(str(ex), f) if im.mode != "P": raise generic.ImageError("image does not have a palette", f) pal = palette.validate_palette(im, f) if forced_palette != "ANY" and pal != forced_palette and not (forced_palette == "DOS" and pal == "WIN"): raise generic.ImageError("Image has '%s' palette, but you forced the '%s' palette" % (pal, used_palette), f) if used_palette == "ANY": used_palette = pal elif pal != used_palette: if used_palette in ("WIN", "DOS") and pal in ("WIN", "DOS"): used_palette = "DOS" else: raise generic.ImageError("Image has '%s' palette, but \"%s\" has the '%s' palette" % (pal, last_file, used_palette), f) last_file = f palette_bytes = {"WIN": "W", "DOS": "D", "ANY": "A"} if used_palette in palette_bytes: grf.set_palette_used(palette_bytes[used_palette]) for outputfile in outputfiles: outputfile.palette = used_palette #If there are any 32bpp sprites hint to openttd that we'd like a 32bpp blitter if alt_sprites.alt_sprites_list: grf.set_preferred_blitter("3") if action8_index != -1: actions = [sprite_count.SpriteCountAction(len(actions))] + actions block_names = {} for idx, action in enumerate(actions): num = start_sprite_num + idx action.prepare_output() if isinstance(action, real_sprite.RealSpriteAction): if action.block_name: block_names[action.block_name] = num if action.sprite_num is not None: if action.sprite_num.value != num: raise generic.ScriptError("Sprite number %d given in base_sprites-block, but it doesn't match output sprite number %d" % (action.sprite_num.value, num)) for outputfile in outputfiles: if isinstance(outputfile, output_base.BinaryOutputBase): outputfile.open() for action in actions: action.write(outputfile) outputfile.close() for block in alt_sprites.alt_sprites_list: block.process(sprites_dir, block_names) return 0 def run(): try: main(sys.argv[1:]) except generic.ScriptError, ex: generic.print_warning("nmlc: %s" % str(ex)) if developmode: raise # Reraise exception in developmode sys.exit(1) except SystemExit, ex: raise except KeyboardInterrupt, ex: generic.print_warning('Application forcibly terminated by user.') if developmode: raise # Reraise exception in developmode sys.exit(1) except Exception, ex: # Other/internal error. if developmode: raise # Reraise exception in developmode # User mode: print user friendly error message. ex_msg = str(ex) if len(ex_msg) > 0: ex_msg = '"%s"' % ex_msg traceback = sys.exc_info()[2] # Walk through the traceback object until we get to the point where the exception happened. while traceback.tb_next is not None: traceback = traceback.tb_next lineno = traceback.tb_lineno frame = traceback.tb_frame code = frame.f_code filename = code.co_filename name = code.co_name del traceback # Required according to Python docs. ex_data = {'class' : ex.__class__.__name__, 'version' : version, 'msg' : ex_msg, 'cli' : sys.argv, 'loc' : 'File "%s", line %d, in %s' % (filename, lineno, name) } msg = "nmlc: An internal error has occurred:\n" \ "nmlc-version: %(version)s\n" \ "Error: (%(class)s) %(msg)s.\n" \ "Command: %(cli)s\n" \ "Location: %(loc)s\n" % ex_data generic.print_warning(msg) sys.exit(1) sys.exit(0) if __name__ == "__main__": run() nml-0.2.4/nml/version_info.py0000644000061700006170000000654312036626441016703 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # The following version determination code is a greatly simplified version # of the mercurial repo code. The version is stored in nml/__version__.py # get_numeric_version is used only for the purpose of packet creation, # in all other cases use get_nml_version() import subprocess, os, sys def get_hg_version(): path = os.path.dirname(os.path.realpath(sys.argv[0])) version = '' if os.path.isdir(os.path.join(path,'.hg')): # we want to return to where we were. So save the old path version_list = (subprocess.Popen(['hg', '-R', path, 'id', '-n', '-t', '-i'], stdout=subprocess.PIPE).communicate()[0]).split() if version_list[1].endswith('+'): modified = 'M' else: modified = '' revision = version_list[1].rstrip('+') hash = version_list[0].rstrip('+') # Test whether we have a tag (=release version) if len(version_list) > 2 and version_list[2] != 'tip': version = version_list[2] else: # we have a trunk version version = 'r'+revision # Add modification tag and hash to the stored version info version += modified + ' (' + hash + ')' return version def get_lib_versions(): versions = {} #PIL try: import Image versions["PIL"] = Image.VERSION except ImportError: versions["PIL"] = "Not found!" #PLY try: from ply import lex versions["PLY"] = lex.__version__ except ImportError: versions["PLY"] = "Not found!" return versions def get_nml_version(): # first try whether we find an nml repository. Use that version, if available version = get_hg_version() if version: return version # no repository was found. Return the version which was saved upon built try: from nml import __version__ version = __version__.version except ImportError: version = 'unknown' return version def get_cli_version(): #version string for usage in command line result = get_nml_version() + "\n" result += "Library versions encountered:\n" for lib, lib_ver in get_lib_versions().items(): result += lib + ": " + lib_ver + "\n" return result[0:-1] #strip trailing newline def get_and_write_version(): version = get_nml_version() if version: try: path = os.path.dirname(os.path.realpath(sys.argv[0])) f = open(os.path.join(path,"nml/__version__.py"), "w") f.write('# this file is autogenerated by setup.py\n') f.write('version = "%s"\n' % version) f.close() return (get_nml_version().split())[0] except IOError: print "Version file NOT written" nml-0.2.4/nml/__init__.py0000644000061700006170000000124312036626441015732 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.2.4/nml/global_constants.py0000644000061700006170000016347512036626442017550 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, nmlop constant_numbers = { #climates 'CLIMATE_TEMPERATE' : 0, 'CLIMATE_ARCTIC' : 1, 'CLIMATE_TROPIC' : 2, 'CLIMATE_TROPICAL' : 2, 'CLIMATE_TOYLAND' : 3, 'ABOVE_SNOWLINE' : 11, # Only for house property available_mask 'NO_CLIMATE' : 0x00, 'ALL_CLIMATES' : 0x0F, #never expire 'VEHICLE_NEVER_EXPIRES' : 0xFF, #cargo classes 'CC_PASSENGERS' : 0, 'CC_MAIL' : 1, 'CC_EXPRESS' : 2, 'CC_ARMOURED' : 3, 'CC_BULK' : 4, 'CC_PIECE_GOODS' : 5, 'CC_LIQUID' : 6, 'CC_REFRIGERATED' : 7, 'CC_HAZARDOUS' : 8, 'CC_COVERED' : 9, 'CC_OVERSIZED' : 10, 'CC_POWDERIZED' : 11, 'CC_NON_POURABLE' : 12, 'CC_NEO_BULK' : 12, 'CC_SPECIAL' : 15, 'NO_CARGO_CLASS' : 0, 'ALL_NORMAL_CARGO_CLASSES' : 0x07FF, 'ALL_CARGO_CLASSES' : 0x87FF, #engine class 'ENGINE_CLASS_STEAM' : 0x00, 'ENGINE_CLASS_DIESEL' : 0x08, 'ENGINE_CLASS_ELECTRIC' : 0x28, 'ENGINE_CLASS_MONORAIL' : 0x32, 'ENGINE_CLASS_MAGLEV' : 0x38, #running costs 'RUNNING_COST_STEAM' : 0x4C30, 'RUNNING_COST_DIESEL' : 0x4C36, 'RUNNING_COST_ELECTRIC' : 0x4C3C, 'RUNNING_COST_ROADVEH' : 0x4C48, 'RUNNING_COST_NONE' : 0x0000, #vehicle cb flags 'VEH_CBF_VISUAL_EFFECT_AND_POWERED' : 0, # trains 'VEH_CBF_VISUAL_EFFECT' : 0, # rvs/ships 'VEH_CBF_WAGON_LENGTH' : 1, 'VEH_CBF_LOAD_AMOUNT' : 2, 'VEH_CBF_REFITTED_CAPACITY' : 3, 'VEH_CBF_ARTICULATED_PARTS' : 4, 'VEH_CBF_CARGO_SUFFIX' : 5, 'VEH_CBF_COLOUR_MAPPING' : 6, 'VEH_CBF_SOUND_EFFECT' : 7, #corresponding callbacks 'VEH_CB_VISUAL_EFFECT_AND_POWERED' : 0x10, # trains 'VEH_CB_VISUAL_EFFECT' : 0x10, # rvs/ships 'VEH_CB_WAGON_LENGTH' : 0x11, 'VEH_CB_LOAD_AMOUNT' : 0x12, 'VEH_CB_REFITTED_CAPACITY' : 0x15, 'VEH_CB_ARTICULATED_PARTS' : 0x16, 'VEH_CB_CARGO_SUFFIX' : 0x19, 'VEH_CB_CAN_ATTACH_WAGON' : 0x1D, 'VEH_CB_TEXT_PURCHASE_SCREEN' : 0x23, 'VEH_CB_COLOUR_MAPPING' : 0x2D, 'VEH_CB_START_STOP_CHECK' : 0x31, 'VEH_CB_32DAY' : 0x32, 'VEH_CB_SOUND_EFFECT' : 0x33, 'VEH_CB_AUTOREPLACE_SELECT' : 0x34, 'VEH_CB_VEHICLE_PROPERTIES' : 0x36, #properties for callback 0x36 'PROP_TRAINS_SPEED' : 0x09, 'PROP_TRAINS_POWER' : 0x0B, 'PROP_TRAINS_RUNNING_COST_FACTOR' : 0x0D, 'PROP_TRAINS_CARGO_CAPACITY' : 0x14, 'PROP_TRAINS_WEIGHT' : 0x16, 'PROP_TRAINS_COST_FACTOR' : 0x17, 'PROP_TRAINS_TRACTIVE_EFFORT_COEFFICIENT' : 0x1F, 'PROP_TRAINS_BITMASK_VEHICLE_INFO' : 0x25, 'PROP_ROADVEHS_RUNNING_COST_FACTOR' : 0x09, 'PROP_ROADVEHS_CARGO_CAPACITY' : 0x0F, 'PROP_ROADVEHS_COST_FACTOR' : 0x11, 'PROP_ROADVEHS_POWER' : 0x13, 'PROP_ROADVEHS_WEIGHT' : 0x14, 'PROP_ROADVEHS_SPEED' : 0x15, 'PROP_ROADVEHS_TRACTIVE_EFFORT_COEFFICIENT' : 0x18, 'PROP_SHIPS_COST_FACTOR' : 0x0A, 'PROP_SHIPS_SPEED' : 0x0B, 'PROP_SHIPS_CARGO_CAPACITY' : 0x0D, 'PROP_SHIPS_RUNNING_COST_FACTOR' : 0x0F, 'PROP_AIRCRAFT_COST_FACTOR' : 0x0B, 'PROP_AIRCRAFT_SPEED' : 0x0C, 'PROP_AIRCRAFT_RUNNING_COST_FACTOR' : 0x0E, 'PROP_AIRCRAFT_PASSENGER_CAPACITY' : 0x0F, 'PROP_AIRCRAFT_MAIL_CAPACITY' : 0x11, #shorten factor 'SHORTEN_TO_8_8' : 0x00, 'SHORTEN_TO_7_8' : 0x01, 'SHORTEN_TO_6_8' : 0x02, 'SHORTEN_TO_5_8' : 0x03, 'SHORTEN_TO_4_8' : 0x04, 'SHORTEN_TO_3_8' : 0x05, 'SHORTEN_TO_2_8' : 0x06, 'SHORTEN_TO_1_8' : 0x07, #visual effect / wagon power 'VISUAL_EFFECT_DEFAULT' : 0x00, 'VISUAL_EFFECT_STEAM' : 0x10, 'VISUAL_EFFECT_DIESEL' : 0x20, 'VISUAL_EFFECT_ELECTRIC' : 0x30, 'VISUAL_EFFECT_DISABLE' : 0x40, 'ENABLE_WAGON_POWER' : 0x00, 'DISABLE_WAGON_POWER' : 0x80, #train misc flags 'TRAIN_FLAG_TILT' : 0, 'TRAIN_FLAG_2CC' : 1, 'TRAIN_FLAG_MU' : 2, 'TRAIN_FLAG_FLIP' : 3, 'TRAIN_FLAG_AUTOREFIT': 4, 'TRAIN_FLAG_NO_BREAKDOWN_SMOKE': 6, #roadveh misc flags 'ROADVEH_FLAG_TRAM' : 0, 'ROADVEH_FLAG_2CC' : 1, 'ROADVEH_FLAG_AUTOREFIT': 4, 'ROADVEH_FLAG_NO_BREAKDOWN_SMOKE': 6, #ship misc flags 'SHIP_FLAG_2CC' : 1, 'SHIP_FLAG_AUTOREFIT': 4, 'SHIP_FLAG_NO_BREAKDOWN_SMOKE': 6, #aircrafts misc flags 'AIRCRAFT_FLAG_2CC' : 1, 'AIRCRAFT_FLAG_AUTOREFIT': 4, 'AIRCRAFT_FLAG_NO_BREAKDOWN_SMOKE': 6, #for those, who can't tell the difference between a train and an aircraft: 'VEHICLE_FLAG_2CC' : 1, 'VEHICLE_FLAG_AUTOREFIT': 4, 'VEHICLE_FLAG_NO_BREAKDOWN_SMOKE': 6, #Graphic flags for waterfeatures 'WATERFEATURE_ALTERNATIVE_SPRITES' : 0, #IDs for waterfeatures 'WF_WATERCLIFFS' : 0x00, 'WF_LOCKS' : 0x01, 'WF_DIKES' : 0x02, 'WF_CANAL_GUI' : 0x03, 'WF_FLAT_DOCKS' : 0x04, 'WF_RIVER_SLOPE' : 0x05, 'WF_RIVERBANKS' : 0x06, 'WF_RIVER_GUI' : 0x07, 'WF_BUOY' : 0x08, #ai flags 'AI_FLAG_PASSENGER' : 0x01, 'AI_FLAG_CARGO' : 0x00, # Callback results 'CB_RESULT_ATTACH_DISALLOW' : 0xFD, 'CB_RESULT_ATTACH_ALLOW' : 0xFE, 'CB_RESULT_ATTACH_ALLOW_IF_RAILTYPES' : 0xFF, 'CB_RESULT_NO_MORE_ARTICULATED_PARTS' : 0xFF, 'CB_RESULT_REVERSED_VEHICLE' : 0x80, 'CB_RESULT_32_DAYS_TRIGGER' : 0, 'CB_RESULT_32_DAYS_COLOUR_MAPPING' : 1, 'CB_RESULT_COLOUR_MAPPING_ADD_CC' : 0x4000, 'CB_RESULT_AUTOREFIT' : 0x4000, 'CB_RESULT_REFIT_COST_MASK' : 0x3FFF, 'CB_RESULT_NO_SOUND' : 0x7EFF, # Never a valid sound id # 1-based, not 0-based 'SOUND_EVENT_START' : 1, 'SOUND_EVENT_TUNNEL' : 2, 'SOUND_EVENT_BREAKDOWN' : 3, 'SOUND_EVENT_RUNNING' : 4, 'SOUND_EVENT_TOUCHDOWN' : 5, 'SOUND_EVENT_VISUAL_EFFECT' : 6, 'SOUND_EVENT_RUNNING_16' : 7, 'SOUND_EVENT_STOPPED' : 8, 'SOUND_EVENT_LOAD_UNLOAD' : 9, #sound effects 'SOUND_SPLAT' : 0x00, 'SOUND_FACTORY_WHISTLE' : 0x01, 'SOUND_TRAIN' : 0x02, 'SOUND_TRAIN_THROUGH_TUNNEL' : 0x03, 'SOUND_SHIP_HORN' : 0x04, 'SOUND_FERRY_HORN' : 0x05, 'SOUND_PLANE_TAKE_OFF' : 0x06, 'SOUND_JET' : 0x07, 'SOUND_TRAIN_HORN' : 0x08, 'SOUND_MINING_MACHINERY' : 0x09, 'SOUND_ELECTRIC_SPARK' : 0x0A, 'SOUND_STEAM' : 0x0B, 'SOUND_LEVEL_CROSSING' : 0x0C, 'SOUND_VEHICLE_BREAKDOWN' : 0x0D, 'SOUND_TRAIN_BREAKDOWN' : 0x0E, 'SOUND_CRASH' : 0x0F, 'SOUND_EXPLOSION' : 0x10, 'SOUND_BIG_CRASH' : 0x11, 'SOUND_CASHTILL' : 0x12, 'SOUND_BEEP' : 0x13, 'SOUND_MORSE' : 0x14, 'SOUND_SKID_PLANE' : 0x15, 'SOUND_HELICOPTER' : 0x16, 'SOUND_BUS_START_PULL_AWAY' : 0x17, 'SOUND_BUS_START_PULL_AWAY_WITH_HORN' : 0x18, 'SOUND_TRUCK_START' : 0x19, 'SOUND_TRUCK_START_2' : 0x1A, 'SOUND_APPLAUSE' : 0x1B, 'SOUND_OOOOH' : 0x1C, 'SOUND_SPLAT_2' : 0x1D, 'SOUND_SPLAT_3' : 0x1E, 'SOUND_JACKHAMMER' : 0x1F, 'SOUND_CAR_HORN' : 0x20, 'SOUND_CAR_HORN_2' : 0x21, 'SOUND_SHEEP' : 0x22, 'SOUND_COW' : 0x23, 'SOUND_HORSE' : 0x24, 'SOUND_BLACKSMITH_ANVIL' : 0x25, 'SOUND_SAWMILL' : 0x26, 'SOUND_GOOD_YEAR' : 0x27, 'SOUND_BAD_YEAR' : 0x28, 'SOUND_RIP' : 0x29, 'SOUND_EXTRACT_AND_POP' : 0x2A, 'SOUND_COMEDY_HIT' : 0x2B, 'SOUND_MACHINERY' : 0x2C, 'SOUND_RIP_2' : 0x2D, 'SOUND_EXTRACT_AND_POP_2' : 0x2E, 'SOUND_POP' : 0x2F, 'SOUND_CARTOON_SOUND' : 0x30, 'SOUND_EXTRACT' : 0x31, 'SOUND_POP_2' : 0x32, 'SOUND_PLASTIC_MINE' : 0x33, 'SOUND_WIND' : 0x34, 'SOUND_COMEDY_BREAKDOWN' : 0x35, 'SOUND_CARTOON_CRASH' : 0x36, 'SOUND_BALLOON_SQUEAK' : 0x37, 'SOUND_CHAINSAW' : 0x38, 'SOUND_HEAVY_WIND' : 0x39, 'SOUND_COMEDY_BREAKDOWN_2' : 0x3A, 'SOUND_JET_OVERHEAD' : 0x3B, 'SOUND_COMEDY_CAR' : 0x3C, 'SOUND_ANOTHER_JET_OVERHEAD' : 0x3D, 'SOUND_COMEDY_CAR_2' : 0x3E, 'SOUND_COMEDY_CAR_3' : 0x3F, 'SOUND_COMEDY_CAR_START_AND_PULL_AWAY' : 0x40, 'SOUND_MAGLEV' : 0x41, 'SOUND_LOON_BIRD' : 0x42, 'SOUND_LION' : 0x43, 'SOUND_MONKEYS' : 0x44, 'SOUND_PLANE_CRASHING' : 0x45, 'SOUND_PLANE_ENGINE_SPUTTERING' : 0x46, 'SOUND_MAGLEV_2' : 0x47, 'SOUND_DISTANT_BIRD' : 0x48, #sprite ids 'SPRITE_ID_NEW_TRAIN' : 0xFD, 'SPRITE_ID_NEW_ROADVEH' : 0xFF, 'SPRITE_ID_NEW_SHIP' : 0xFF, 'SPRITE_ID_NEW_AIRCRAFT' : 0xFF, 'NEW_CARGO_SPRITE' : 0xFFFF, #aircraft type/size 'AIRCRAFT_TYPE_NORMAL' : 0x02, 'AIRCRAFT_TYPE_HELICOPTER' : 0x00, 'AIRCRAFT_SIZE_SMALL' : 0x00, 'AIRCRAFT_SIZE_LARGE' : 0x01, #ground sprite IDs 'GROUNDSPRITE_CONCRETE' : 1420, 'GROUNDSPRITE_CLEARED' : 3924, 'GROUNDSPRITE_NORMAL' : 3981, 'GROUNDSPRITE_WATER' : 4061, 'GROUNDSPRITE_SNOW' : 4550, 'GROUNDSPRITE_DESERT' : 4550, # general CB for rerandomizing 'CB_RANDOM_TRIGGER' : 0x01, #house callback flags 'HOUSE_CBF_BUILD' : 0, 'HOUSE_CBF_ANIM_NEXT_FRAME' : 1, 'HOUSE_CBF_ANIM_CONROL' : 2, 'HOUSE_CBF_CONSTRUCTION_ANIM' : 3, 'HOUSE_CBF_COLOUR' : 4, 'HOUSE_CBF_CARGO_AMOUNT_ACCEPT' : 5, 'HOUSE_CBF_ANIM_SPEED' : 6, 'HOUSE_CBF_DESTRUCTION' : 7, 'HOUSE_CBF_CARGO_TYPE_ACCEPT' : 8, 'HOUSE_CBF_CARGO_PRODUCTION' : 9, 'HOUSE_CBF_PROTECTION' : 10, 'HOUSE_CBF_FOUNDATIONS' : 11, 'HOUSE_CBF_AUTOSLOPE' : 12, #corresponding callbacks 'HOUSE_CB_BUILD' : 0x17, 'HOUSE_CB_ANIM_NEXT_FRAME' : 0x1A, 'HOUSE_CB_ANIM_CONTROL' : 0x1B, 'HOUSE_CB_CONSTRUCTION_ANIM' : 0x1C, 'HOUSE_CB_COLOUR' : 0x1E, 'HOUSE_CB_CARGO_AMOUNT_ACCEPT' : 0x1F, 'HOUSE_CB_ANIM_SPEED' : 0x20, 'HOUSE_CB_DESTRUCTION' : 0x21, 'HOUSE_CB_CARGO_TYPE_ACCEPT' : 0x2A, 'HOUSE_CB_CARGO_PRODUCTION' : 0x2E, 'HOUSE_CB_PROTECTION' : 0x143, 'HOUSE_CB_ACCEPTED_CARGO' : 0x148, 'HOUSE_CB_BUILDING_NAME' : 0x14D, 'HOUSE_CB_FOUNDATIONS' : 0x14E, 'HOUSE_CB_AUTOSLOPE' : 0x14F, #house flags 'HOUSE_FLAG_SIZE_1x1' : 0, 'HOUSE_FLAG_NOT_SLOPED' : 1, 'HOUSE_FLAG_SIZE_2x1' : 2, 'HOUSE_FLAG_SIZE_1x2' : 3, 'HOUSE_FLAG_SIZE_2x2' : 4, 'HOUSE_FLAG_ANIMATE' : 5, 'HOUSE_FLAG_CHURCH' : 6, 'HOUSE_FLAG_STADIUM' : 7, 'HOUSE_FLAG_ONLY_SE' : 8, 'HOUSE_FLAG_PROTECTED' : 9, 'HOUSE_FLAG_SYNC_CALLBACK' : 10, 'HOUSE_FLAG_RANDOM_ANIMATION' : 11, #cargo acceptance 'HOUSE_ACCEPT_GOODS' : 0x00, 'HOUSE_ACCEPT_FOOD' : 0x10, # 0x80 / 8 'HOUSE_ACCEPT_FIZZY_DRINKS' : 0x10, # 0x80 / 8 #town zones 'TOWNZONE_EDGE' : 0, 'TOWNZONE_OUTSKIRT' : 1, 'TOWNZONE_OUTER_SUBURB' : 2, 'TOWNZONE_INNER_SUBURB' : 3, 'TOWNZONE_CENTRE' : 4, 'ALL_TOWNZONES' : 0x1F, #industry tile callback flags 'INDTILE_CBF_ANIM_NEXT_FRAME' : 0, 'INDTILE_CBF_ANIM_SPEED' : 1, 'INDTILE_CBF_CARGO_AMOUNT_ACCEPT' : 2, 'INDTILE_CBF_CARGO_TYPE_ACCEPT' : 3, 'INDTILE_CBF_SLOPE_IS_SUITABLE' : 4, 'INDTILE_CBF_FOUNDATIONS' : 5, 'INDTILE_CBF_AUTOSLOPE' : 6, #corresponding callbacks 'INDTILE_CB_ANIM_CONTROL' : 0x25, 'INDTILE_CB_ANIM_NEXT_FRAME' : 0x26, 'INDTILE_CB_ANIM_SPEED' : 0x27, 'INDTILE_CB_CARGO_AMOUNT_ACCEPT' : 0x2B, 'INDTILE_CB_CARGO_TYPE_ACCEPT' : 0x2C, 'INDTILE_CB_SLOPE_IS_SUITABLE' : 0x2F, 'INDTILE_CB_FOUNDATIONS' : 0x30, 'INDTILE_CB_AUTOSLOPE' : 0x3C, #industry tile special flags 'INDTILE_FLAG_RANDOM_ANIMATION' : 0, #industry callback flags 'IND_CBF_AVAILABILITY' : 0, 'IND_CBF_PROD_CB_CARGO_ARRIVE' : 1, 'IND_CBF_PROD_CB_256_TICKS' : 2, 'IND_CBF_LOCATION_CHECK' : 3, 'IND_CBF_RANDOM_PROD_CHANGE' : 4, 'IND_CBF_MONTHLY_PROD_CHANGE' : 5, 'IND_CBF_CARGO_SUBTYPE_DISPLAY' : 6, 'IND_CBF_EXTRA_TEXT_FUND' : 7, 'IND_CBF_EXTRA_TEXT_INDUSTRY' : 8, 'IND_CBF_CONTROL_SPECIAL' : 9, 'IND_CBF_STOP_ACCEPT_CARGO' : 10, 'IND_CBF_COLOUR' : 11, 'IND_CBF_CARGO_INPUT' : 12, 'IND_CBF_CARGO_OUTPUT' : 13, #corresponding callbacks 'IND_CB_AVAILABILITY' : 0x22, 'IND_CB_LOCATION_CHECK' : 0x28, 'IND_CB_RANDOM_PROD_CHANGE' : 0x29, 'IND_CB_MONTHLY_PROD_CHANGE' : 0x35, 'IND_CB_CARGO_SUBTYPE_DISPLAY' : 0x37, 'IND_CB_EXTRA_TEXT_FUND' : 0x38, 'IND_CB_EXTRA_TEXT_INDUSTRY' : 0x3A, 'IND_CB_CONTROL_SPECIAL' : 0x3B, 'IND_CB_STOP_ACCEPT_CARGO' : 0x3D, 'IND_CB_COLOUR' : 0x14A, 'IND_CB_CARGO_INPUT' : 0x14B, 'IND_CB_CARGO_OUTPUT' : 0x14C, 'CB_RESULT_IND_PROD_NO_CHANGE' : 0x00, 'CB_RESULT_IND_PROD_HALF' : 0x01, 'CB_RESULT_IND_PROD_DOUBLE' : 0x02, 'CB_RESULT_IND_PROD_CLOSE' : 0x03, 'CB_RESULT_IND_PROD_RANDOM' : 0x04, 'CB_RESULT_IND_PROD_DIVIDE_BY_4' : 0x05, 'CB_RESULT_IND_PROD_DIVIDE_BY_8' : 0x06, 'CB_RESULT_IND_PROD_DIVIDE_BY_16' : 0x07, 'CB_RESULT_IND_PROD_DIVIDE_BY_32' : 0x08, 'CB_RESULT_IND_PROD_MULTIPLY_BY_4' : 0x09, 'CB_RESULT_IND_PROD_MULTIPLY_BY_8' : 0x0A, 'CB_RESULT_IND_PROD_MULTIPLY_BY_16' : 0x0B, 'CB_RESULT_IND_PROD_MULTIPLY_BY_32' : 0x0C, 'CB_RESULT_IND_PROD_DECREMENT_BY_1' : 0x0D, 'CB_RESULT_IND_PROD_INCREMENT_BY_1' : 0x0E, 'CB_RESULT_IND_PROD_SET_BY_0x100' : 0x0F, 'CB_RESULT_IND_DO_NOT_USE_SPECIAL' : 0x00, 'CB_RESULT_IND_USE_SPECIAL' : 0x01, 'CB_RESULT_IND_ALLOW' : 0x00, 'CB_RESULT_IND_DISALLOW' : 0x01, 'CB_RESULT_NO_TEXT' : 0xFF, 'CB_RESULT_LOCATION_ALLOW' : 0x400, 'CB_RESULT_LOCATION_DISALLOW' : 0x401, 'CB_RESULT_LOCATION_DISALLOW_ONLY_RAINFOREST' : 0x402, 'CB_RESULT_LOCATION_DISALLOW_ONLY_DESERT' : 0x403, 'CB_RESULT_LOCATION_DISALLOW_ONLY_ABOVE_SNOWLINE' : 0x404, 'CB_RESULT_LOCATION_DISALLOW_ONLY_BELOW_SNOWLINE' : 0x405, 'CB_RESULT_LOCATION_DISALLOW_NOT_ON_OPEN_SEA' : 0x406, 'CB_RESULT_LOCATION_DISALLOW_NOT_ON_CANAL' : 0x407, 'CB_RESULT_LOCATION_DISALLOW_NOT_ON_RIVER' : 0x408, #industry special flags 'IND_FLAG_PLANT_FIELDS_PERIODICALLY' : 0, 'IND_FLAG_CUT_TREES' : 1, 'IND_FLAG_BUILT_ON_WATER' : 2, 'IND_FLAG_ONLY_IN_LARGE_TOWNS' : 3, 'IND_FLAG_ONLY_IN_TOWNS' : 4, 'IND_FLAG_BUILT_NEAR_TOWN' : 5, 'IND_FLAG_PLANT_FIELDS_WHEN_BUILT' : 6, 'IND_FLAG_NO_PRODUCTION_INCREASE' : 7, 'IND_FLAG_BUILT_ONLY_BEFORE_1950' : 8, 'IND_FLAG_BUILT_ONLY_AFTER_1960' : 9, 'IND_FLAG_AI_CREATES_AIR_AND_SHIP_ROUTES' : 10, 'IND_FLAG_MILITARY_AIRPLANE_CAN_EXPLODE' : 11, 'IND_FLAG_MILITARY_HELICOPTER_CAN_EXPLODE' : 12, 'IND_FLAG_CAN_CAUSE_SUBSIDENCE' : 13, 'IND_FLAG_AUTOMATIC_PRODUCTION_MULTIPLIER' : 14, 'IND_FLAG_RANDOM_BITS_IN_PRODUCTION_CALLBACK' : 15, 'IND_FLAG_DO_NOT_FORCE_INSTANCE_AT_MAP_GENERATION' : 16, 'IND_FLAG_ALLOW_CLOSING_LAST_INSTANCE' : 17, #flags for builtin function industry_type(..) 'IND_TYPE_OLD' : 0, 'IND_TYPE_NEW' : 1, #founder for industries (founder variable) 'FOUNDER_GAME' : 16, #object flags 'OBJ_FLAG_ONLY_SE' : 0, 'OBJ_FLAG_IRREMOVABLE' : 1, 'OBJ_FLAG_ANYTHING_REMOVE' : 2, 'OBJ_FLAG_ON_WATER' : 3, 'OBJ_FLAG_REMOVE_IS_INCOME': 4, 'OBJ_FLAG_NO_FOUNDATIONS' : 5, 'OBJ_FLAG_ANIMATED' : 6, 'OBJ_FLAG_ONLY_INGAME' : 7, 'OBJ_FLAG_2CC' : 8, 'OBJ_FLAG_NOT_ON_LAND' : 9, 'OBJ_FLAG_DRAW_WATER' : 10, 'OBJ_FLAG_ALLOW_BRIDGE' : 11, 'OBJ_FLAG_RANDOM_ANIMATION': 12, #object animation triggers 'OBJ_ANIM_IS_BUILT' : 0, 'OBJ_ANIM_PERIODIC' : 1, 'OBJ_ANIM_SYNC' : 2, #object callback flags 'OBJ_CBF_SLOPE_CHECK' : 0, 'OBJ_CBF_ANIM_NEXT_FRAME' : 1, 'OBJ_CBF_ANIM_SPEED' : 2, 'OBJ_CBF_DECIDE_COLOUR' : 3, 'OBJ_CBF_ADDITIONAL_TEXT' : 4, 'OBJ_CBF_AUTOSLOPE' : 5, #corresponding callbacks 'OBJ_CB_SLOPE_CHECK' : 0x157, 'OBJ_CB_ANIM_NEXT_FRAME' : 0x158, 'OBJ_CB_ANIM_CONTROL' : 0x159, 'OBJ_CB_ANIM_SPEED' : 0x15A, 'OBJ_CB_DECIDE_COLOUR' : 0x15B, 'OBJ_CB_ADDITIONAL_TEXT' : 0x15C, 'OBJ_CB_AUTOSLOPE' : 0x15D, # Special values for object var 0x60 'OBJECT_TYPE_OTHER_GRF' : 0xFFFE, 'OBJECT_TYPE_NO_OBJECT' : 0xFFFF, #airport tile callback flags 'APT_CBF_ANIM_NEXT_FRAME' : 0, 'APT_CBF_ANIM_SPEED' : 1, 'APT_CBF_SLOPE_CHECK' : 4, 'APT_CBF_FOUNDATIONS' : 5, 'APT_CBF_AUTOSLOPE' : 6, #corresponding callbacks 'APT_CB_ANIM_CONTROL' : 0x152, 'APT_CB_ANIM_NEXT_FRAME' : 0x153, 'APT_CB_ANIM_SPEED' : 0x154, 'APT_CB_FOUNDATIONS' : 0x150, #Airport callbacks 'AIRPORT_CB_ADDITIONAL_TEXT' : 0x155, 'AIRPORT_CB_LAYOUT_NAME' : 0x156, #railtype flags 'RAILTYPE_FLAG_CATENARY' : 0, 'RAILTYPE_FLAG_NO_LEVEL_CROSSING' : 1, # for OpenTTD > r20049 #type of default station graphics used for a railtype 'RAILTYPE_STATION_NORMAL' : 0, 'RAILTYPE_STATION_MONORAIL' : 1, 'RAILTYPE_STATION_MAGLEV' : 2, #ground tile types as returned by railtypes varaction2 0x40 'TILETYPE_NORMAL' : 0x00, 'TILETYPE_DESERT' : 0x01, 'TILETYPE_RAIN_FOREST' : 0x02, 'TILETYPE_SNOW' : 0x04, # Water classes 'WATER_CLASS_NONE' : 0, 'WATER_CLASS_SEA' : 1, 'WATER_CLASS_CANAL' : 2, 'WATER_CLASS_RIVER' : 3, #level crossing status as returned by railtypes varaction2 0x42 'LEVEL_CROSSING_CLOSED' : 1, 'LEVEL_CROSSING_OPEN' : 0, 'LEVEL_CROSSING_NONE' : 0, #acceleration model for trains 'ACC_MODEL_RAIL' : 0, 'ACC_MODEL_MONORAIL' : 1, 'ACC_MODEL_MAGLEV' : 2, #default industry IDs 'INDUSTRYTYPE_COAL_MINE' : 0x00, 'INDUSTRYTYPE_POWER_PLANT' : 0x01, 'INDUSTRYTYPE_SAWMILL' : 0x02, 'INDUSTRYTYPE_FOREST' : 0x03, 'INDUSTRYTYPE_OIL_REFINERY' : 0x04, 'INDUSTRYTYPE_OIL_RIG' : 0x05, 'INDUSTRYTYPE_TEMPERATE_FACTORY' : 0x06, 'INDUSTRYTYPE_PRINTING_WORKS' : 0x07, 'INDUSTRYTYPE_STEEL_MILL' : 0x08, 'INDUSTRYTYPE_TEMPERATE_ARCTIC_FARM' : 0x09, 'INDUSTRYTYPE_COPPER_ORE_MINE' : 0x0A, 'INDUSTRYTYPE_OIL_WELLS' : 0x0B, 'INDUSTRYTYPE_TEMPERATE_BANK' : 0x0C, 'INDUSTRYTYPE_FOOD_PROCESSING_PLANT' : 0x0D, 'INDUSTRYTYPE_PAPER_MILL' : 0x0E, 'INDUSTRYTYPE_GOLD_MINE' : 0x0F, 'INDUSTRYTYPE_TROPICAL_ARCTIC_BANK' : 0x10, 'INDUSTRYTYPE_DIAMOND_MINE' : 0x11, 'INDUSTRYTYPE_IRON_ORE_MINE' : 0x12, 'INDUSTRYTYPE_FRUIT_PLANTATION' : 0x13, 'INDUSTRYTYPE_RUBBER_PLANTATION' : 0x14, 'INDUSTRYTYPE_WATER_WELL' : 0x15, 'INDUSTRYTYPE_WATER_TOWER' : 0x16, 'INDUSTRYTYPE_TROPICAL_FACTORY' : 0x17, 'INDUSTRYTYPE_TROPICAL_FARM' : 0x18, 'INDUSTRYTYPE_LUMBER_MILL' : 0x19, 'INDUSTRYTYPE_CANDYFLOSS_FOREST' : 0x1A, 'INDUSTRYTYPE_SWEETS_FACTORY' : 0x1B, 'INDUSTRYTYPE_BATTERY_FARM' : 0x1C, 'INDUSTRYTYPE_COLA_WELLS' : 0x1D, 'INDUSTRYTYPE_TOY_SHOP' : 0x1E, 'INDUSTRYTYPE_TOY_FACTORY' : 0x1F, 'INDUSTRYTYPE_PLASTIC_FOUNTAIN' : 0x20, 'INDUSTRYTYPE_FIZZY_DRINKS_FACTORY' : 0x21, 'INDUSTRYTYPE_BUBBLE_GENERATOR' : 0x22, 'INDUSTRYTYPE_TOFFE_QUARRY' : 0x23, 'INDUSTRYTYPE_SUGAR_MINE' : 0x24, 'INDUSTRYTYPE_UNKNOWN' : 0xFE, 'INDUSTRYTYPE_TOWN' : 0xFF, #industry life types (industry property 0x0B, life_type) 'IND_LIFE_TYPE_BLACK_HOLE' : 0x00, 'IND_LIFE_TYPE_EXTRACTIVE' : 0x01, 'IND_LIFE_TYPE_ORGANIC' : 0x02, 'IND_LIFE_TYPE_PROCESSING' : 0x04, #traffic side (bool, true = right hand side) 'TRAFFIC_SIDE_LEFT' : 0, 'TRAFFIC_SIDE_RIGHT' : 1, #which platform has loaded this grf 'PLATFORM_TTDPATCH' : 0x00, 'PLATFORM_OPENTTD' : 0x01, #player types (vehicle var 0x43) 'PLAYERTYPE_HUMAN' : 0, 'PLAYERTYPE_AI' : 1, 'PLAYERTYPE_HUMAN_IN_AI' : 2, 'PLAYERTYPE_AI_IN_HUMAN' : 3, #build types (industry var 0xB3) 'BUILDTYPE_UNKNOWN' : 0, 'BUILDTYPE_GAMEPLAY' : 1, 'BUILDTYPE_GENERATION' : 2, 'BUILDTYPE_EDITOR' : 3, # Creation types (several industry[tile] callbacks 'IND_CREATION_GENERATION' : 0, 'IND_CREATION_RANDOM' : 1, 'IND_CREATION_FUND' : 2, 'IND_CREATION_PROSPECT' : 3, #airport types (vehicle var 0x44) 'AIRPORTTYPE_SMALL' : 0, 'AIRPORTTYPE_LARGE' : 1, 'AIRPORTTYPE_HELIPORT' : 2, 'AIRPORTTYPE_OILRIG' : 3, #Direction for e.g. airport layouts, vehicle direction 'DIRECTION_NORTH' : 0, 'DIRECTION_NORTHEAST' : 1, 'DIRECTION_EAST' : 2, 'DIRECTION_SOUTHEAST' : 3, 'DIRECTION_SOUTH' : 4, 'DIRECTION_SOUTHWEST' : 5, 'DIRECTION_WEST' : 6, 'DIRECTION_NORTHWEST' : 7, 'CORNER_W' : 0, 'CORNER_S' : 1, 'CORNER_E' : 2, 'CORNER_N' : 3, 'IS_STEEP_SLOPE' : 4, 'SLOPE_FLAT' : 0, 'SLOPE_W' : 1, 'SLOPE_S' : 2, 'SLOPE_E' : 4, 'SLOPE_N' : 8, 'SLOPE_NW' : 9, 'SLOPE_SW' : 3, 'SLOPE_SE' : 6, 'SLOPE_NE' : 12, 'SLOPE_EW' : 5, 'SLOPE_NS' : 10, 'SLOPE_NWS' : 11, 'SLOPE_WSE' : 7, 'SLOPE_SEN' : 14, 'SLOPE_ENW' : 13, 'SLOPE_STEEP_W' : 27, 'SLOPE_STEEP_S' : 23, 'SLOPE_STEEP_E' : 30, 'SLOPE_STEEP_N' : 29, #loading stages 'LOADINGSTAGE_INITIALIZE' : 0x0000, 'LOADINGSTAGE_RESERVE' : 0x0101, 'LOADINGSTAGE_ACTIVATE' : 0x0201, 'LOADINGSTAGE_TEST' : 0x0401, #palettes 'PALETTE_DOS' : 0, 'PALETTE_WIN' : 1, #game mode 'GAMEMODE_MENU' : 0, 'GAMEMODE_GAME' : 1, 'GAMEMODE_EDITOR' : 2, #difficulty 'DIFFICULTY_EASY' : 0, 'DIFFICULTY_MEDIUM' : 1, 'DIFFICULTY_HARD' : 2, 'DIFFICULTY_CUSTOM' : 3, #display options 'DISPLAY_TOWN_NAMES' : 0, 'DISPLAY_STATION_NAMES' : 1, 'DISPLAY_SIGNS' : 2, 'DISPLAY_ANIMATION' : 3, 'DISPLAY_FULL_DETAIL' : 5, #map types (ttdp variable 0x13) 'MAP_TYPE_X_BIGGER' : 0, #bit 0 and 1 clear 'MAP_TYPE_RECTANGULAR' : 1, #bit 0 set 'MAP_TYPE_Y_BIGGER' : 2, #bit 0 clear, bit 1 set #Random triggers 'TRIGGER_ALL_NEEDED' : 7, 'TRIGGER_VEHICLE_NEW_LOAD' : 0, 'TRIGGER_VEHICLE_SERVICE' : 1, 'TRIGGER_VEHICLE_UNLOAD_ALL' : 2, 'TRIGGER_VEHICLE_ANY_LOAD' : 3, 'TRIGGER_VEHICLE_32_CALLBACK' : 4, 'TRIGGER_STATION_NEW_CARGO' : 0, 'TRIGGER_STATION_NO_MORE_CARGO' : 1, 'TRIGGER_STATION_TRAIN_ARRIVES' : 2, 'TRIGGER_STATION_TRAIN_LEAVES' : 3, 'TRIGGER_STATION_TRAIN_LOADS_UNLOADS' : 4, 'TRIGGER_STATION_TRAIN_RESERVES' : 5, 'TRIGGER_HOUSE_TILELOOP' : 0, 'TRIGGER_HOUSE_TOP_TILELOOP' : 1, 'TRIGGER_INDUSTRYTILE_TILELOOP' : 0, 'TRIGGER_INDUSTRYTILE_256_TICKS' : 1, 'TRIGGER_INDUSTRYTILE_CARGO_DELIVERY' : 2, #Tile classes 'TILE_CLASS_GROUND' : 0x00, 'TILE_CLASS_RAIL' : 0x01, 'TILE_CLASS_ROAD' : 0x02, 'TILE_CLASS_HOUSE' : 0x03, 'TILE_CLASS_TREES' : 0x04, 'TILE_CLASS_STATION' : 0x05, 'TILE_CLASS_WATER' : 0x06, 'TILE_CLASS_INDUSTRY' : 0x08, 'TILE_CLASS_TUNNEL_BRIDGE' : 0x09, 'TILE_CLASS_OBJECTS' : 0x0A, #Land shape flags for industry tiles 'LSF_CANNOT_LOWER_NW_EDGE' : 0, 'LSF_CANNOT_LOWER_NE_EDGE' : 1, 'LSF_CANNOT_LOWER_SW_EDGE' : 2, 'LSF_CANNOT_LOWER_SE_EDGE' : 3, 'LSF_ONLY_ON_FLAT_LAND' : 4, 'LSF_ALLOW_ON_WATER' : 5, # Animation triggers 'ANIM_TRIGGER_INDTILE_CONSTRUCTION_STATE' : 0, 'ANIM_TRIGGER_INDTILE_TILE_LOOP' : 1, 'ANIM_TRIGGER_INDTILE_INDUSTRY_LOOP' : 2, 'ANIM_TRIGGER_INDTILE_RECEIVED_CARGO' : 3, 'ANIM_TRIGGER_INDTILE_DISTRIBUTES_CARGO' : 4, 'ANIM_TRIGGER_OBJ_BUILT' : 0, 'ANIM_TRIGGER_OBJ_TILELOOP' : 1, 'ANIM_TRIGGER_OBJ_256_TICKS' : 2, 'ANIM_TRIGGER_APT_BUILT' : 0, 'ANIM_TRIGGER_APT_TILELOOP' : 1, 'ANIM_TRIGGER_APT_NEW_CARGO' : 2, 'ANIM_TRIGGER_APT_CARGO_TAKEN' : 3, 'ANIM_TRIGGER_APT_250_TICKS' : 4, # Animation looping 'ANIMATION_NON_LOOPING' : 0, 'ANIMATION_LOOPING' : 1, # Animation callback results 'CB_RESULT_STOP_ANIMATION' : 0xFF, # callback 0x25, 0x26 'CB_RESULT_START_ANIMATION' : 0xFE, # callback 0x25 'CB_RESULT_NEXT_FRAME' : 0xFE, # callback 0x26 'CB_RESULT_DO_NOTHING' : 0xFD, # callback 0x25 'CB_RESULT_FOUNDATIONS' : 0x01, # callback 0x30 'CB_RESULT_NO_FOUNDATIONS' : 0x00, # callback 0x30 'CB_RESULT_AUTOSLOPE' : 0x00, # callback 0x3C 'CB_RESULT_NO_AUTOSLOPE' : 0x01, # callback 0x3C #Zoom levels 'ZOOM_LEVEL_NORMAL' : 2, 'ZOOM_LEVEL_Z0' : 0, 'ZOOM_LEVEL_Z1' : 1, 'ZOOM_LEVEL_Z2' : 2, # Recolour modes for layout sprites 'RECOLOUR_NONE' : 0, 'RECOLOUR_TRANSPARENT' : 1, 'RECOLOUR_REMAP' : 2, # Possible values for palette 'PALETTE_USE_DEFAULT' : 0, 'PALETTE_TILE_RED_PULSATING' : 771, 'PALETTE_SEL_TILE_RED' : 772, 'PALETTE_SEL_TILE_BLUE' : 773, 'PALETTE_CC_FIRST' : 775, 'PALETTE_CC_DARK_BLUE' : 775, # = first 'PALETTE_CC_PALE_GREEN' : 776, 'PALETTE_CC_PINK' : 777, 'PALETTE_CC_YELLOW' : 778, 'PALETTE_CC_RED' : 778, 'PALETTE_CC_LIGHT_BLUE' : 780, 'PALETTE_CC_GREEN' : 781, 'PALETTE_CC_DARK_GREEN' : 782, 'PALETTE_CC_BLUE' : 783, 'PALETTE_CC_CREAM' : 784, 'PALETTE_CC_MAUVE' : 785, 'PALETTE_CC_PURPLE' : 786, 'PALETTE_CC_ORANGE' : 787, 'PALETTE_CC_BROWN' : 788, 'PALETTE_CC_GREY' : 789, 'PALETTE_CC_WHITE' : 790, 'PALETTE_BARE_LAND' : 791, 'PALETTE_STRUCT_BLUE' : 795, 'PALETTE_STRUCT_BROWN' : 796, 'PALETTE_STRUCT_WHITE' : 797, 'PALETTE_STRUCT_RED' : 798, 'PALETTE_STRUCT_GREEN' : 799, 'PALETTE_STRUCT_CONCRETE' : 800, 'PALETTE_STRUCT_YELLOW' : 801, 'PALETTE_TRANSPARENT' : 802, 'PALETTE_STRUCT_GREY' : 803, 'PALETTE_CRASH' : 804, 'PALETTE_CHURCH_RED' : 1438, 'PALETTE_CHURCH_CREAM' : 1439, # Company colours 'COLOUR_DARK_BLUE' : 0, 'COLOUR_PALE_GREEN' : 1, 'COLOUR_PINK' : 2, 'COLOUR_YELLOW' : 3, 'COLOUR_RED' : 4, 'COLOUR_LIGHT_BLUE' : 5, 'COLOUR_GREEN' : 6, 'COLOUR_DARK_GREEN' : 7, 'COLOUR_BLUE' : 8, 'COLOUR_CREAM' : 9, 'COLOUR_MAUVE' : 10, 'COLOUR_PURPLE' : 11, 'COLOUR_ORANGE' : 12, 'COLOUR_BROWN' : 13, 'COLOUR_GREY' : 14, 'COLOUR_WHITE' : 15, # Town growth effect of cargo 'TOWNGROWTH_PASSENGERS' : 0x00, 'TOWNGROWTH_MAIL' : 0x02, 'TOWNGROWTH_GOODS' : 0x05, 'TOWNGROWTH_WATER' : 0x09, 'TOWNGROWTH_FOOD' : 0x0B, 'TOWNGROWTH_NONE' : 0xFF, # Cargo callbacks 'CARGO_CB_PROFIT' : 0x01, 'CARGO_CB_STATION_RATING' : 0x02, #CMP and UCMP results 'CMP_LESS' : 0, 'CMP_EQUAL' : 1, 'CMP_GREATER' : 2, # TTD Strings 'TTD_STR_CARGO_PLURAL_NOTHING' : 0x000E, 'TTD_STR_CARGO_PLURAL_PASSENGERS' : 0x000F, 'TTD_STR_CARGO_PLURAL_COAL' : 0x0010, 'TTD_STR_CARGO_PLURAL_MAIL' : 0x0011, 'TTD_STR_CARGO_PLURAL_OIL' : 0x0012, 'TTD_STR_CARGO_PLURAL_LIVESTOCK' : 0x0013, 'TTD_STR_CARGO_PLURAL_GOODS' : 0x0014, 'TTD_STR_CARGO_PLURAL_GRAIN' : 0x0015, 'TTD_STR_CARGO_PLURAL_WOOD' : 0x0016, 'TTD_STR_CARGO_PLURAL_IRON_ORE' : 0x0017, 'TTD_STR_CARGO_PLURAL_STEEL' : 0x0018, 'TTD_STR_CARGO_PLURAL_VALUABLES' : 0x0019, 'TTD_STR_CARGO_PLURAL_COPPER_ORE' : 0x001A, 'TTD_STR_CARGO_PLURAL_MAIZE' : 0x001B, 'TTD_STR_CARGO_PLURAL_FRUIT' : 0x001C, 'TTD_STR_CARGO_PLURAL_DIAMONDS' : 0x001D, 'TTD_STR_CARGO_PLURAL_FOOD' : 0x001E, 'TTD_STR_CARGO_PLURAL_PAPER' : 0x001F, 'TTD_STR_CARGO_PLURAL_GOLD' : 0x0020, 'TTD_STR_CARGO_PLURAL_WATER' : 0x0021, 'TTD_STR_CARGO_PLURAL_WHEAT' : 0x0022, 'TTD_STR_CARGO_PLURAL_RUBBER' : 0x0023, 'TTD_STR_CARGO_PLURAL_SUGAR' : 0x0024, 'TTD_STR_CARGO_PLURAL_TOYS' : 0x0025, 'TTD_STR_CARGO_PLURAL_CANDY' : 0x0026, 'TTD_STR_CARGO_PLURAL_COLA' : 0x0027, 'TTD_STR_CARGO_PLURAL_COTTON_CANDY' : 0x0028, 'TTD_STR_CARGO_PLURAL_BUBBLES' : 0x0029, 'TTD_STR_CARGO_PLURAL_TOFFEE' : 0x002A, 'TTD_STR_CARGO_PLURAL_BATTERIES' : 0x002B, 'TTD_STR_CARGO_PLURAL_PLASTIC' : 0x002C, 'TTD_STR_CARGO_PLURAL_FIZZY_DRINKS' : 0x002D, 'TTD_STR_CARGO_SINGULAR_NOTHING' : 0x002E, 'TTD_STR_CARGO_SINGULAR_PASSENGER' : 0x002F, 'TTD_STR_CARGO_SINGULAR_COAL' : 0x0030, 'TTD_STR_CARGO_SINGULAR_MAIL' : 0x0031, 'TTD_STR_CARGO_SINGULAR_OIL' : 0x0032, 'TTD_STR_CARGO_SINGULAR_LIVESTOCK' : 0x0033, 'TTD_STR_CARGO_SINGULAR_GOODS' : 0x0034, 'TTD_STR_CARGO_SINGULAR_GRAIN' : 0x0035, 'TTD_STR_CARGO_SINGULAR_WOOD' : 0x0036, 'TTD_STR_CARGO_SINGULAR_IRON_ORE' : 0x0037, 'TTD_STR_CARGO_SINGULAR_STEEL' : 0x0038, 'TTD_STR_CARGO_SINGULAR_VALUABLES' : 0x0039, 'TTD_STR_CARGO_SINGULAR_COPPER_ORE' : 0x003A, 'TTD_STR_CARGO_SINGULAR_MAIZE' : 0x003B, 'TTD_STR_CARGO_SINGULAR_FRUIT' : 0x003C, 'TTD_STR_CARGO_SINGULAR_DIAMOND' : 0x003D, 'TTD_STR_CARGO_SINGULAR_FOOD' : 0x003E, 'TTD_STR_CARGO_SINGULAR_PAPER' : 0x003F, 'TTD_STR_CARGO_SINGULAR_GOLD' : 0x0040, 'TTD_STR_CARGO_SINGULAR_WATER' : 0x0041, 'TTD_STR_CARGO_SINGULAR_WHEAT' : 0x0042, 'TTD_STR_CARGO_SINGULAR_RUBBER' : 0x0043, 'TTD_STR_CARGO_SINGULAR_SUGAR' : 0x0044, 'TTD_STR_CARGO_SINGULAR_TOY' : 0x0045, 'TTD_STR_CARGO_SINGULAR_CANDY' : 0x0046, 'TTD_STR_CARGO_SINGULAR_COLA' : 0x0047, 'TTD_STR_CARGO_SINGULAR_COTTON_CANDY' : 0x0048, 'TTD_STR_CARGO_SINGULAR_BUBBLE' : 0x0049, 'TTD_STR_CARGO_SINGULAR_TOFFEE' : 0x004A, 'TTD_STR_CARGO_SINGULAR_BATTERY' : 0x004B, 'TTD_STR_CARGO_SINGULAR_PLASTIC' : 0x004C, 'TTD_STR_CARGO_SINGULAR_FIZZY_DRINK' : 0x004D, 'TTD_STR_PASSENGERS' : 0x004F, 'TTD_STR_TONS' : 0x0050, 'TTD_STR_BAGS' : 0x0051, 'TTD_STR_LITERS' : 0x0052, 'TTD_STR_ITEMS' : 0x0053, 'TTD_STR_CRATES' : 0x0054, 'TTD_STR_QUANTITY_NOTHING' : 0x006E, 'TTD_STR_QUANTITY_PASSENGERS' : 0x006F, 'TTD_STR_QUANTITY_COAL' : 0x0070, 'TTD_STR_QUANTITY_MAIL' : 0x0071, 'TTD_STR_QUANTITY_OIL' : 0x0072, 'TTD_STR_QUANTITY_LIVESTOCK' : 0x0073, 'TTD_STR_QUANTITY_GOODS' : 0x0074, 'TTD_STR_QUANTITY_GRAIN' : 0x0075, 'TTD_STR_QUANTITY_WOOD' : 0x0076, 'TTD_STR_QUANTITY_IRON_ORE' : 0x0077, 'TTD_STR_QUANTITY_STEEL' : 0x0078, 'TTD_STR_QUANTITY_VALUABLES' : 0x0079, 'TTD_STR_QUANTITY_COPPER_ORE' : 0x007A, 'TTD_STR_QUANTITY_MAIZE' : 0x007B, 'TTD_STR_QUANTITY_FRUIT' : 0x007C, 'TTD_STR_QUANTITY_DIAMONDS' : 0x007D, 'TTD_STR_QUANTITY_FOOD' : 0x007E, 'TTD_STR_QUANTITY_PAPER' : 0x007F, 'TTD_STR_QUANTITY_GOLD' : 0x0080, 'TTD_STR_QUANTITY_WATER' : 0x0081, 'TTD_STR_QUANTITY_WHEAT' : 0x0082, 'TTD_STR_QUANTITY_RUBBER' : 0x0083, 'TTD_STR_QUANTITY_SUGAR' : 0x0084, 'TTD_STR_QUANTITY_TOYS' : 0x0085, 'TTD_STR_QUANTITY_SWEETS' : 0x0086, 'TTD_STR_QUANTITY_COLA' : 0x0087, 'TTD_STR_QUANTITY_CANDYFLOSS' : 0x0088, 'TTD_STR_QUANTITY_BUBBLES' : 0x0089, 'TTD_STR_QUANTITY_TOFFEE' : 0x008A, 'TTD_STR_QUANTITY_BATTERIES' : 0x008B, 'TTD_STR_QUANTITY_PLASTIC' : 0x008C, 'TTD_STR_QUANTITY_FIZZY_DRINKS' : 0x008D, 'TTD_STR_ABBREV_NOTHING' : 0x008E, 'TTD_STR_ABBREV_PASSENGERS' : 0x008F, 'TTD_STR_ABBREV_COAL' : 0x0090, 'TTD_STR_ABBREV_MAIL' : 0x0091, 'TTD_STR_ABBREV_OIL' : 0x0092, 'TTD_STR_ABBREV_LIVESTOCK' : 0x0093, 'TTD_STR_ABBREV_GOODS' : 0x0094, 'TTD_STR_ABBREV_GRAIN' : 0x0095, 'TTD_STR_ABBREV_WOOD' : 0x0096, 'TTD_STR_ABBREV_IRON_ORE' : 0x0097, 'TTD_STR_ABBREV_STEEL' : 0x0098, 'TTD_STR_ABBREV_VALUABLES' : 0x0099, 'TTD_STR_ABBREV_COPPER_ORE' : 0x009A, 'TTD_STR_ABBREV_MAIZE' : 0x009B, 'TTD_STR_ABBREV_FRUIT' : 0x009C, 'TTD_STR_ABBREV_DIAMONDS' : 0x009D, 'TTD_STR_ABBREV_FOOD' : 0x009E, 'TTD_STR_ABBREV_PAPER' : 0x009F, 'TTD_STR_ABBREV_GOLD' : 0x00A0, 'TTD_STR_ABBREV_WATER' : 0x00A1, 'TTD_STR_ABBREV_WHEAT' : 0x00A2, 'TTD_STR_ABBREV_RUBBER' : 0x00A3, 'TTD_STR_ABBREV_SUGAR' : 0x00A4, 'TTD_STR_ABBREV_TOYS' : 0x00A5, 'TTD_STR_ABBREV_SWEETS' : 0x00A6, 'TTD_STR_ABBREV_COLA' : 0x00A7, 'TTD_STR_ABBREV_CANDYFLOSS' : 0x00A8, 'TTD_STR_ABBREV_BUBBLES' : 0x00A9, 'TTD_STR_ABBREV_TOFFEE' : 0x00AA, 'TTD_STR_ABBREV_BATTERIES' : 0x00AB, 'TTD_STR_ABBREV_PLASTIC' : 0x00AC, 'TTD_STR_ABBREV_FIZZY_DRINKS' : 0x00AD, 'TTD_STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_1' : 0x200F, 'TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_1' : 0x2010, 'TTD_STR_TOWN_BUILDING_NAME_SMALL_BLOCK_OF_FLATS_1' : 0x2011, 'TTD_STR_TOWN_BUILDING_NAME_CHURCH_1' : 0x2012, 'TTD_STR_TOWN_BUILDING_NAME_LARGE_OFFICE_BLOCK_1' : 0x2013, 'TTD_STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1' : 0x2014, 'TTD_STR_TOWN_BUILDING_NAME_HOTEL_1' : 0x2015, 'TTD_STR_TOWN_BUILDING_NAME_STATUE_1' : 0x2016, 'TTD_STR_TOWN_BUILDING_NAME_FOUNTAIN_1' : 0x2017, 'TTD_STR_TOWN_BUILDING_NAME_PARK_1' : 0x2018, 'TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_2' : 0x2019, 'TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_1' : 0x201A, 'TTD_STR_TOWN_BUILDING_NAME_MODERN_OFFICE_BUILDING_1' : 0x201B, 'TTD_STR_TOWN_BUILDING_NAME_WAREHOUSE_1' : 0x201C, 'TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_3' : 0x201D, 'TTD_STR_TOWN_BUILDING_NAME_STADIUM_1' : 0x201E, 'TTD_STR_TOWN_BUILDING_NAME_OLD_HOUSES_1' : 0x201F, 'TTD_STR_TOWN_BUILDING_NAME_COTTAGES_1' : 0x2036, 'TTD_STR_TOWN_BUILDING_NAME_HOUSES_1' : 0x2037, 'TTD_STR_TOWN_BUILDING_NAME_FLATS_1' : 0x2038, 'TTD_STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_2' : 0x2039, 'TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_2' : 0x203A, 'TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_3' : 0x203B, 'TTD_STR_TOWN_BUILDING_NAME_THEATER_1' : 0x203C, 'TTD_STR_TOWN_BUILDING_NAME_STADIUM_2' : 0x203D, 'TTD_STR_TOWN_BUILDING_NAME_OFFICES_1' : 0x203E, 'TTD_STR_TOWN_BUILDING_NAME_HOUSES_2' : 0x203F, 'TTD_STR_TOWN_BUILDING_NAME_CINEMA_1' : 0x2040, 'TTD_STR_TOWN_BUILDING_NAME_SHOPPING_MALL_1' : 0x2041, 'TTD_STR_TOWN_BUILDING_NAME_IGLOO_1' : 0x2059, 'TTD_STR_TOWN_BUILDING_NAME_TEPEES_1' : 0x205A, 'TTD_STR_TOWN_BUILDING_NAME_TEAPOT_HOUSE_1' : 0x205B, 'TTD_STR_TOWN_BUILDING_NAME_PIGGY_BANK_1' : 0x205C, 'TTD_STR_INDUSTRY_NAME_COAL_MINE' : 0x4802, 'TTD_STR_INDUSTRY_NAME_POWER_STATION' : 0x4803, 'TTD_STR_INDUSTRY_NAME_SAWMILL' : 0x4804, 'TTD_STR_INDUSTRY_NAME_FOREST' : 0x4805, 'TTD_STR_INDUSTRY_NAME_OIL_REFINERY' : 0x4806, 'TTD_STR_INDUSTRY_NAME_OIL_RIG' : 0x4807, 'TTD_STR_INDUSTRY_NAME_FACTORY' : 0x4808, 'TTD_STR_INDUSTRY_NAME_PRINTING_WORKS' : 0x4809, 'TTD_STR_INDUSTRY_NAME_STEEL_MILL' : 0x480A, 'TTD_STR_INDUSTRY_NAME_FARM' : 0x480B, 'TTD_STR_INDUSTRY_NAME_COPPER_ORE_MINE' : 0x480C, 'TTD_STR_INDUSTRY_NAME_OIL_WELLS' : 0x480D, 'TTD_STR_INDUSTRY_NAME_BANK' : 0x480E, 'TTD_STR_INDUSTRY_NAME_FOOD_PROCESSING_PLANT' : 0x480F, 'TTD_STR_INDUSTRY_NAME_PAPER_MILL' : 0x4810, 'TTD_STR_INDUSTRY_NAME_GOLD_MINE' : 0x4811, 'TTD_STR_INDUSTRY_NAME_BANK_TROPIC_ARCTIC' : 0x4812, 'TTD_STR_INDUSTRY_NAME_DIAMOND_MINE' : 0x4813, 'TTD_STR_INDUSTRY_NAME_IRON_ORE_MINE' : 0x4814, 'TTD_STR_INDUSTRY_NAME_FRUIT_PLANTATION' : 0x4815, 'TTD_STR_INDUSTRY_NAME_RUBBER_PLANTATION' : 0x4816, 'TTD_STR_INDUSTRY_NAME_WATER_SUPPLY' : 0x4817, 'TTD_STR_INDUSTRY_NAME_WATER_TOWER' : 0x4818, 'TTD_STR_INDUSTRY_NAME_FACTORY_2' : 0x4819, 'TTD_STR_INDUSTRY_NAME_FARM_2' : 0x481A, 'TTD_STR_INDUSTRY_NAME_LUMBER_MILL' : 0x481B, 'TTD_STR_INDUSTRY_NAME_COTTON_CANDY_FOREST' : 0x481C, 'TTD_STR_INDUSTRY_NAME_CANDY_FACTORY' : 0x481D, 'TTD_STR_INDUSTRY_NAME_BATTERY_FARM' : 0x481E, 'TTD_STR_INDUSTRY_NAME_COLA_WELLS' : 0x481F, 'TTD_STR_INDUSTRY_NAME_TOY_SHOP' : 0x4820, 'TTD_STR_INDUSTRY_NAME_TOY_FACTORY' : 0x4821, 'TTD_STR_INDUSTRY_NAME_PLASTIC_FOUNTAINS' : 0x4822, 'TTD_STR_INDUSTRY_NAME_FIZZY_DRINK_FACTORY' : 0x4823, 'TTD_STR_INDUSTRY_NAME_BUBBLE_GENERATOR' : 0x4824, 'TTD_STR_INDUSTRY_NAME_TOFFEE_QUARRY' : 0x4825, 'TTD_STR_INDUSTRY_NAME_SUGAR_MINE' : 0x4826, 'TTD_STR_NEWS_INDUSTRY_CONSTRUCTION' : 0x482D, 'TTD_STR_NEWS_INDUSTRY_PLANTED' : 0x482E, 'TTD_STR_NEWS_INDUSTRY_CLOSURE_GENERAL' : 0x4832, 'TTD_STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS' : 0x4833, 'TTD_STR_NEWS_INDUSTRY_CLOSURE_LACK_OF_TREES' : 0x4834, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_GENERAL' : 0x4835, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_COAL' : 0x4836, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_OIL' : 0x4837, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_FARM' : 0x4838, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_GENERAL' : 0x4839, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_FARM' : 0x483A, 'TTD_STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY' : 0x4830, 'TTD_STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED' : 0x4831, 'TTD_STR_ERROR_CAN_ONLY_BE_POSITIONED' : 0x483B, } def signextend(param, info): #r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info['size'] * 8 - 1)) return expression.BinOp(nmlop.SUB, expression.BinOp(nmlop.XOR, param, m, param.pos), m, param.pos) def global_param_write(info, expr, pos): if not ('writable' in info and info['writable']): raise generic.ScriptError("Target parameter is not writable.", pos) return expression.Parameter(expression.ConstantNumeric(info['num']), pos), expr def global_param_read(info, pos): param = expression.Parameter(expression.ConstantNumeric(info['num']), pos) if info['size'] == 1: mask = expression.ConstantNumeric(0xFF) param = expression.BinOp(nmlop.AND, param, mask) else: assert info['size'] == 4 if 'function' in info: return info['function'](param, info) return param def param_from_info(info, pos): return expression.SpecialParameter(generic.reverse_lookup(global_parameters, info), info, global_param_write, global_param_read, False, pos) global_parameters = { 'climate' : {'num': 0x83, 'size': 1}, 'loading_stage' : {'num': 0x84, 'size': 4}, 'ttdpatch_version' : {'num': 0x8B, 'size': 4}, 'current_palette' : {'num': 0x8D, 'size': 1}, 'traininfo_y_offset' : {'num': 0x8E, 'size': 1, 'writable': 1, 'function': signextend}, 'game_mode' : {'num': 0x92, 'size': 1}, 'ttd_platform' : {'num': 0x9D, 'size': 4}, 'openttd_version' : {'num': 0xA1, 'size': 4}, 'difficulty_level' : {'num': 0xA2, 'size': 4}, 'date_loaded' : {'num': 0xA3, 'size': 4}, 'year_loaded' : {'num': 0xA4, 'size': 4}, } def misc_bit_write(info, expr, pos): param = expression.Parameter(expression.ConstantNumeric(info['param'], pos), pos) #param = (expr != 0) ? param | (1 << bit) : param & ~(1 << bit) expr = expression.BinOp(nmlop.CMP_NEQ, expr, expression.ConstantNumeric(0, pos), pos) or_expr = expression.BinOp(nmlop.OR, param, expression.ConstantNumeric(1 << info['bit'], pos), pos) and_expr = expression.BinOp(nmlop.AND, param, expression.ConstantNumeric(~(1 << info['bit']), pos), pos) expr = expression.TernaryOp(expr, or_expr, and_expr, pos) return (param, expr) def misc_bit_read(info, pos): return expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(info['param'], pos), pos), expression.ConstantNumeric(info['bit'], pos), pos) def misc_grf_bit(info, pos): return expression.SpecialParameter(generic.reverse_lookup(misc_grf_bits, info), info, misc_bit_write, misc_bit_read, True, pos) misc_grf_bits = { 'traffic_side' : {'param': 0x86, 'bit': 4}, 'desert_paved_roads' : {'param': 0x9E, 'bit': 1}, 'train_width_32_px' : {'param': 0x9E, 'bit': 3}, } def add_1920(expr, info): """ Create a new expression that adds 1920 to a given expression. @param expr: The expression to add 1920 to. @type expr: L{Expression} @param info: Ignored. @return: A new expression that adds 1920 to the given expression. @rtype: L{Expression} """ return expression.BinOp(nmlop.ADD, expr, expression.ConstantNumeric(1920, expr.pos), expr.pos) def map_exponentiate(expr, info): """ Given a exponent, add an offset ot it and compute the exponentiation with base 2. @param expr: The exponent. @type expr: L{Expression} @param info: Table with extra information, most notable 'log_offset', the value we need to add to the given expression before computing the exponentiation. @type info: C{dict} @return: An expression computing 2**(expr + info['log_offset']). @rtype: L{Expression} """ #map (log2(x) - a) to x, i.e. do 1 << (x + a) expr = expression.BinOp(nmlop.ADD, expr, expression.ConstantNumeric(info['log_offset'], expr.pos), expr.pos) return expression.BinOp(nmlop.SHIFT_LEFT, expression.ConstantNumeric(1, expr.pos), expr, expr.pos) def patch_variable_read(info, pos): """ Helper function to read special patch variables. @param info: Generic information about the parameter to read, like parameter number and size. @type info: C{dict} @param pos: Position information in the source file. @type pos: L{Position} or C{None} @return: An expression that reads the special variables. @rtype: L{Expression} """ expr = expression.PatchVariable(info['num'], pos) if info['start'] != 0: expr = expression.BinOp(nmlop.SHIFT_RIGHT, expr, expression.ConstantNumeric(info['start'], pos), pos) if info['size'] != 32: expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric((1 << info['size']) - 1, pos), pos) if 'function' in info: expr = info['function'](expr, info) return expr def patch_variable(info, pos): return expression.SpecialParameter(generic.reverse_lookup(patch_variables, info), info, None, patch_variable_read, False, pos) patch_variables = { 'starting_year' : {'num': 0x0B, 'start': 0, 'size': 32, 'function': add_1920}, 'freight_trains' : {'num': 0x0E, 'start': 0, 'size': 32}, 'plane_speed' : {'num': 0x10, 'start': 0, 'size': 32}, 'base_sprite_2cc' : {'num': 0x11, 'start': 0, 'size': 32}, 'map_type' : {'num': 0x13, 'start': 24, 'size': 2}, 'map_min_edge' : {'num': 0x13, 'start': 20, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_max_edge' : {'num': 0x13, 'start': 16, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_x_edge' : {'num': 0x13, 'start': 12, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_y_edge' : {'num': 0x13, 'start': 8, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_size' : {'num': 0x13, 'start': 0, 'size': 8, 'log_offset': 12, 'function': map_exponentiate}, } def config_flag_read(bit, pos): return expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(0x85), pos), expression.ConstantNumeric(bit), pos) def config_flag(info, pos): return expression.SpecialParameter(generic.reverse_lookup(config_flags, info), info, None, config_flag_read, True, pos) config_flags = { 'long_bridges' : 0x0F, 'gradual_loading' : 0x2C, 'bridge_speed_limits' : 0x34, 'newtrains' : 0x37, 'newrvs' : 0x38, 'newships' : 0x39, 'newplanes' : 0x3A, 'signals_on_traffic_side' : 0x3B, 'electrified_railways' : 0x3C, 'newhouses' : 0x59, 'newindustries' : 0x67, 'temperate_snowline' : 0x6A, 'newcargos' : 0x6B, 'dynamic_engines' : 0x78, 'variable_runningcosts' : 0x7E, } def unified_maglev_read(info, pos): bit0 = expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(0x85), pos), expression.ConstantNumeric(0x32), pos) bit1 = expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(0x85), pos), expression.ConstantNumeric(0x33), pos) shifted_bit1 = expression.BinOp(nmlop.SHIFT_LEFT, bit1, expression.ConstantNumeric(1)) return expression.BinOp(nmlop.OR, shifted_bit1, bit0) def unified_maglev(info, pos): return expression.SpecialParameter(generic.reverse_lookup(unified_maglev_var, info), info, None, unified_maglev_read, False, pos) unified_maglev_var = { 'unified_maglev' : 0, } def setting_from_info(info, pos): return expression.SpecialParameter(generic.reverse_lookup(settings, info), info, global_param_write, global_param_read, False, pos) def item_to_id(item, pos): if not isinstance(item.id, expression.ConstantNumeric): raise generic.ScriptError("Referencing item '%s' with a non-constant id is not possible." % item.name, pos) return expression.ConstantNumeric(item.id.value, pos) def param_from_name(info, pos): return expression.Parameter(expression.ConstantNumeric(info), pos) def create_spritegroup_ref(info, pos): return expression.SpriteGroupRef(expression.Identifier(info), [], pos) cargo_numbers = {} railtype_table = {'RAIL': 0, 'ELRL': 1, 'MONO': 1, 'MGLV': 2} item_names = {} settings = {} named_parameters = {} spritegroups = {'CB_FAILED': 'CB_FAILED'} const_list = [ constant_numbers, (global_parameters, param_from_info), (misc_grf_bits, misc_grf_bit), (patch_variables, patch_variable), (named_parameters, param_from_name), cargo_numbers, railtype_table, (item_names, item_to_id), (settings, setting_from_info), (config_flags, config_flag), (unified_maglev_var, unified_maglev), (spritegroups, create_spritegroup_ref), ] nml-0.2.4/nml/nmlop.py0000644000061700006170000002371412036626442015330 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import operator from .expression.base_expression import Type, ConstantNumeric, ConstantFloat from nml import generic class Operator(object): def __init__(self, act2_supports = False, act2_str = None, act2_num = None, actd_supports = False, actd_str = None, actd_num = None, returns_boolean = False, token = None, compiletime_func = None, validate_func = None): self.act2_supports = act2_supports self.act2_str = act2_str self.act2_num = act2_num self.actd_supports = actd_supports self.actd_str = actd_str self.actd_num = actd_num self.returns_boolean = returns_boolean self.token = token self.compiletime_func = compiletime_func self.validate_func = validate_func def to_string(self, expr1, expr2): return '(%s %s %s)' % (expr1, self.token, expr2) def unsigned_rshift(a, b): if a < 0: a += 0x100000000 return generic.truncate_int32(a >> b) def unsigned_rrotate(a, b): if a < 0: a += 0x100000000 return generic.truncate_int32((a >> b) | (a << (32 - b))) def validate_func_int(expr1, expr2, pos): if expr1.type() != Type.INTEGER or expr2.type() != Type.INTEGER: raise generic.ScriptError("Binary operator requires both operands to be integers.", pos) def validate_func_float(expr1, expr2, pos): if expr1.type() not in (Type.INTEGER, Type.FLOAT) or expr2.type() not in (Type.INTEGER, Type.FLOAT): raise generic.ScriptError("Binary operator requires both operands to be integers or floats.", pos) # If one is a float, the other must be constant since we can't handle floats at runtime if (expr1.type() == Type.FLOAT and not isinstance(expr2, (ConstantNumeric, ConstantFloat))) or \ (expr2.type() == Type.FLOAT and not isinstance(expr1, (ConstantNumeric, ConstantFloat))): raise generic.ScriptError("Floating-point operations are only possible when both operands are compile-time constants.", pos) def validate_func_add(expr1, expr2, pos): if (expr1.type() == Type.STRING_LITERAL) ^ (expr2.type() == Type.STRING_LITERAL): raise generic.ScriptError("Concatenating a string literal and a number is not possible.", pos) if expr1.type() != Type.STRING_LITERAL: validate_func_float(expr1, expr2, pos) def validate_func_div_mod(expr1, expr2, pos): validate_func_float(expr1, expr2, pos) if isinstance(expr2, (ConstantNumeric, ConstantFloat)) and expr2.value == 0: raise generic.ScriptError("Division and modulo require the right hand side to be nonzero.", pos) def validate_func_rhs_positive(expr1, expr2, pos): validate_func_int(expr1, expr2, pos) if isinstance(expr2, ConstantNumeric) and expr2.value < 0: raise generic.ScriptError("Right hand side of the operator may not be a negative number.", pos) ADD = Operator( act2_supports = True, act2_str = r'\2+', act2_num = 0, actd_supports = True, actd_str = r'\D+', actd_num = 1, token = '+', compiletime_func = operator.add, validate_func = validate_func_add, ) SUB = Operator( act2_supports = True, act2_str = r'\2-', act2_num = 1, actd_supports = True, actd_str = r'\D-', actd_num = 2, token = '-', compiletime_func = operator.sub, validate_func = validate_func_float, ) DIV = Operator( act2_supports = True, act2_str = r'\2/', act2_num = 6, actd_supports = True, actd_str = r'\D/', actd_num = 10, token = '/', compiletime_func = lambda a, b: a // b if isinstance(a, int) and isinstance(b, int) else a / b, validate_func = validate_func_div_mod, ) MOD = Operator( act2_supports = True, act2_str = r'\2%', act2_num = 7, actd_supports = True, actd_str = r'\D%', actd_num = 12, token = '%', compiletime_func = operator.mod, validate_func = validate_func_div_mod, ) MUL = Operator( act2_supports = True, act2_str = r'\2*', act2_num = 10, actd_supports = True, actd_str = r'\D*', actd_num = 4, token = '*', compiletime_func = operator.mul, validate_func = validate_func_float, ) AND = Operator( act2_supports = True, act2_str = r'\2&', act2_num = 11, actd_supports = True, actd_str = r'\D&', actd_num = 7, token = '&', compiletime_func = operator.and_, validate_func = validate_func_int, ) OR = Operator( act2_supports = True, act2_str = r'\2|', act2_num = 12, actd_supports = True, actd_str = r'\D|', actd_num = 8, token = '|', compiletime_func = operator.or_, validate_func = validate_func_int, ) XOR = Operator( act2_supports = True, act2_str = r'\2^', act2_num = 13, actd_supports = True, token = '^', compiletime_func = operator.xor, validate_func = validate_func_int, ) CMP_EQ = Operator( act2_supports = True, actd_supports = True, token = '==', compiletime_func = operator.eq, validate_func = validate_func_float, ) CMP_NEQ = Operator( act2_supports = True, actd_supports = True, token = '!=', compiletime_func = operator.ne, validate_func = validate_func_float, ) CMP_LE = Operator( act2_supports = True, actd_supports = True, token = '<=', compiletime_func = operator.le, validate_func = validate_func_float, ) CMP_GE = Operator( act2_supports = True, actd_supports = True, token = '>=', compiletime_func = operator.ge, validate_func = validate_func_float, ) CMP_LT = Operator( act2_supports = True, actd_supports = True, token = '<', compiletime_func = operator.lt, validate_func = validate_func_float, ) CMP_GT = Operator( act2_supports = True, actd_supports = True, token = '>', compiletime_func = operator.gt, validate_func = validate_func_float, ) MIN = Operator( act2_supports = True, act2_str = r'\2<', act2_num = 2, actd_supports = True, compiletime_func = lambda a, b: min(a, b), validate_func = validate_func_float, ) MAX = Operator( act2_supports = True, act2_str = r'\2>', act2_num = 3, actd_supports = True, compiletime_func = lambda a, b: max(a, b), validate_func = validate_func_float, ) STO_TMP = Operator( act2_supports = True, act2_str = r'\2sto', act2_num = 14, validate_func = validate_func_rhs_positive, ) STO_PERM = Operator( act2_supports = True, act2_str = r'\2psto', act2_num = 16, validate_func = validate_func_rhs_positive, ) SHIFT_LEFT = Operator( act2_supports = True, act2_str = r'\2<<', act2_num = 20, actd_supports = True, actd_str = r'\D<<', actd_num = 6, token = '<<', compiletime_func = operator.lshift, validate_func = validate_func_rhs_positive, ) SHIFT_RIGHT = Operator( act2_supports = True, act2_str = r'\2>>', act2_num = 22, actd_supports = True, token = '>>', compiletime_func = operator.rshift, validate_func = validate_func_rhs_positive, ) SHIFTU_RIGHT = Operator( act2_supports = True, act2_str = r'\2u>>', act2_num = 21, actd_supports = True, token = '>>>', compiletime_func = unsigned_rshift, validate_func = validate_func_rhs_positive, ) HASBIT = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, compiletime_func = lambda a, b: (a & (1 << b)) != 0, validate_func = validate_func_rhs_positive, ) #A few operators that are generated internally but can't be directly written in nml NOTHASBIT = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, compiletime_func = lambda a, b: (a & (1 << b)) == 0, ) VAL2 = Operator( act2_supports = True, act2_str = r'\2r', act2_num = 15, compiletime_func = lambda a, b: b, ) ASSIGN = Operator( actd_supports = True, actd_str = r'\D=', actd_num = 0, ) SHIFTU_LEFT = Operator( actd_supports = True, actd_str = r'\Du<<', actd_num = 5, token = '<<', ) VACT2_CMP = Operator( act2_supports = True, act2_str = r'\2cmp', act2_num = 18, ) VACT2_UCMP = Operator( act2_supports = True, act2_str = r'\2ucmp', act2_num = 19, ) MINU = Operator( act2_supports = True, act2_str = r'\2u<', act2_num = 4, ) ROT_RIGHT = Operator( act2_supports = True, act2_str = r'\2ror', act2_num = 17, compiletime_func = unsigned_rrotate, validate_func = validate_func_int, ) DIVU = Operator( act2_supports = True, act2_str = r'\2u/', act2_num = 8, actd_supports = True, actd_str = r'\Du/', actd_num = 9, ) MIN.to_string = lambda expr1, expr2: 'min(%s, %s)' % (expr1, expr2) MAX.to_string = lambda expr1, expr2: 'max(%s, %s)' % (expr1, expr2) STO_TMP.to_string = lambda expr1, expr2: 'STORE_TEMP(%s, %s)' % (expr1, expr2) STO_PERM.to_string = lambda expr1, expr2: 'STORE_PERM(%s, %s)' % (expr1, expr2) HASBIT.to_string = lambda expr1, expr2: 'hasbit(%s, %s)' % (expr1, expr2) NOTHASBIT.to_string = lambda expr1, expr2: '!hasbit(%s, %s)' % (expr1, expr2) VACT2_CMP.to_string = lambda expr1, expr2: 'CMP(%s, %s)' % (expr1, expr2) VACT2_UCMP.to_string = lambda expr1, expr2: 'UCMP(%s, %s)' % (expr1, expr2) ROT_RIGHT.to_string = lambda expr1, expr2: 'rotate(%s, %s)' % (expr1, expr2) class GRMOperator(object): def __init__(self, op_str, op_num): self.op_str = op_str self.op_num = op_num self.value = op_num def __str__(self): return self.op_str def write(self, file, size): assert size == 1 file.print_bytex(self.op_num, self.op_str) GRM_RESERVE = GRMOperator(r'\DR', 0) nml-0.2.4/nml/__version__.py0000644000061700006170000000011212036626604016447 0ustar abuildabuild00000000000000# this file is autogenerated by setup.py version = "0.2.4 (abf432e8d9f8)" nml-0.2.4/nml/output_base.py0000644000061700006170000001371312036626442016533 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Abstract base classes that implements common functionality for output classes """ import StringIO class OutputBase(object): """ Base class for output to a data file. The file is opened with L{open}. Once that is done, data can be written using the L{file} data member. When finished writing, the file should be closed with L{close}. Derived classes should implement L{open_file} to perform the actual opening of the file. L{pre_close} is called to warn them of pending closure of the file. @ivar filename: Name of the data file. @type filename: C{str} @ivar file: Output file handle, if opened. @type file: C{file} or C{None} """ def __init__(self, filename): self.filename = filename self.file = None def open(self): """ Open the output file. Data gets stored in-memory. """ self.file = StringIO.StringIO() def open_file(self): """ Construct the file handle of the output file. @return: File handle of the opened file. @rtype: C{file} """ raise NotImplementedError("Implement me in %s" % type(self)) def pre_close(self): """ File is about to be closed, last chance to append data. """ pass def close(self): """ Close the file, copy collected output to the real file. """ self.pre_close() real_file = self.open_file() real_file.write(self.file.getvalue()) real_file.close() self.file.close() def skip_sprite_checks(self): """ Return wether sprites need detailed parsing. """ return False class BinaryOutputBase(OutputBase): """ Base class for output to a binary data file. @ivar _in_sprite: Set to true if we are currently outputting a sprite. Outputting anything when not in a sprite causes an assert. @type _in_sprite: C{bool} @ivar _byte_count: Number of bytes written in the current sprite. @type _byte_count: C{int} @ivar _expected_count: Number of bytes expected in the current sprite. @type _expected_count: C{int} """ def __init__(self, filename): OutputBase.__init__(self, filename) self._in_sprite = False self._expected_count = 0 self._byte_count = 0 def pre_close(self): assert not self._in_sprite def prepare_byte(self, value): assert self._in_sprite if -0x80 <= value < 0 : value += 0x100 assert value >= 0 and value <= 0xFF self._byte_count += 1 return value def prepare_word(self, value): assert self._in_sprite if -0x8000 <= value < 0: value += 0x10000 assert value >= 0 and value <= 0xFFFF self._byte_count += 2 return value def prepare_dword(self, value): assert self._in_sprite if -0x80000000 <= value < 0: value += 0x100000000 assert value >= 0 and value <= 0xFFFFFFFF self._byte_count += 4 return value def print_varx(self, value, size): if size == 1: self.print_bytex(value) elif size == 2: self.print_wordx(value) elif size == 3: self.print_bytex(0xFF) self.print_wordx(value) elif size == 4: self.print_dwordx(value) else: assert False def print_bytex(self, byte, pretty_print = None): """ Output an unsigned byte. @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_bytex() in %r" % type(self)) def print_wordx(self, byte): """ Output an unsigned word (2 bytes). @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_wordx() in %r" % type(self)) def print_dwordx(self, byte): """ Output an unsigned double word (4 bytes). @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_dwordx() in %r" % type(self)) def newline(self, msg = "", prefix = "\t"): """ Output a line separator, prefixed with L{prefix}, C{"// "}, and the L{msg}, if the latter is not empty. @param msg: Optional message to output first. @type msg: C{str} @param prefix: Additional white space in front of the comment. @type prefix: C{str} """ raise NotImplementedError("Implement newline() in %r" % type(self)) def comment(self, msg): """ Output a textual comment at a line by itself. @param msg: Comment message. @type msg: C{str} @note: Only use if no bytes have been written to the current line. """ raise NotImplementedError("Implement comment() in %r" % type(self)) def start_sprite(self, size): assert not self._in_sprite self._in_sprite = True self._expected_count = size self._byte_count = 0 def end_sprite(self): assert self._in_sprite self._in_sprite = False self.newline() assert self._expected_count == self._byte_count, "Expected %d bytes to be written to sprite, got %d" % (self._expected_count, self._byte_count) nml-0.2.4/nml/output_nml.py0000644000061700006170000000210212036626441016374 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import codecs from nml import output_base class OutputNML(output_base.OutputBase): """ Class for outputting NML. """ def __init__(self, filename): output_base.OutputBase.__init__(self, filename) def open_file(self): return codecs.open(self.filename, 'w', 'utf-8') def write(self, text): self.file.write(text) def newline(self): self.file.write('\n') nml-0.2.4/nml/grfstrings.py0000644000061700006170000011141212036626442016364 0ustar abuildabuild00000000000000from __future__ import with_statement __license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import os, codecs, glob from nml import generic def utf8_get_size(char): if char < 128: return 1 if char < 2048: return 2 if char < 65536: return 3 return 4 DEFAULT_LANGUAGE = 0x7F def validate_string(string): """ Check if a given string refers to a string that is translated in the language files and raise an error otherwise. @param string: The string to validate. @type string: L{expression.String} """ if string.name.value not in default_lang.strings: raise generic.ScriptError('Unknown string "%s"' % string.name.value, string.pos) def is_ascii_string(string): """ Check whether a given string can be written using the ASCII codeset or that we need unicode. @param string: The string to check. @type string: C{basestring} @return: True iff the string is ascii-only. @rtype: C{bool} """ assert isinstance(string, basestring) i = 0 while i < len(string): if string[i] != '\\': if ord(string[i]) >= 0x7B: return False i += 1 else: if string[i+1] in ('\\', '"'): i += 2 elif string[i+1] == 'U': return False else: i += 3 return True def get_string_size(string, final_zero = True, force_ascii = False): """ Get the size (in bytes) of a given string. @param string: The string to check. @type string: C{basestring} @param final_zero: Whether or not to account for a zero-byte directly after the string. @type final_zero: C{bool} @param force_ascii: When true, make sure the string is written as ascii as opposed to unicode. @type force_ascii: C{bool} @return: The length (in bytes) of the given string. @rtype: C{int} @raise generic.ScriptError: force_ascii and not is_ascii_string(string). """ size = 0 if final_zero: size += 1 if not is_ascii_string(string): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") size += 2 i = 0 while i < len(string): if string[i] != '\\': size += utf8_get_size(ord(string[i])) i += 1 else: if string[i+1] in ('\\', '"'): size += 1 i += 2 elif string[i+1] == 'U': size += utf8_get_size(int(string[i+2:i+6], 16)) i += 6 else: size += 1 i += 3 return size def get_translation(string, lang_id = DEFAULT_LANGUAGE): """ Get the translation of a given string in a certain language. If there is no translation available in the given language return the default translation. @param string: the string to get the translation for. @type string: L{expression.String} @param lang_id: The language id of the language to translate the string into. @type lang_id: C{int} @return: Translation of the given string in the given language. @rtype: C{unicode} """ for lang_pair in langs: langid, lang = lang_pair if langid != lang_id: continue if string.name.value not in lang.strings: break return lang.get_string(string) return default_lang.get_string(string) def get_translations(string): """ Get a list of language ids that have a translation for the given string. @param string: the string to get the translations for. @type string: L{expression.String} @return: List of languages that translate the given string. @rtype: C{list} of C{int} """ translations = [] for lang_pair in langs: langid, lang = lang_pair assert langid is not None if string.name.value in lang.strings and lang.get_string(string) != default_lang.get_string(string): translations.append(langid) return translations def com_parse_comma(val, lang_id): val = val.reduce_constant() return str(val) def com_parse_hex(val, lang_id): val = val.reduce_constant() return "0x%X" % val.value def com_parse_string(val, lang_id): import nml.expression if not isinstance(val, (nml.expression.StringLiteral, nml.expression.String)): raise generic.ScriptError("Expected a (literal) string", val.pos) if isinstance(val, nml.expression.String): # Check that the string exists if val.name.value not in default_lang.strings: raise generic.ScriptError("Substring \"%s\" does not exist" % val.name.value, val.pos) return get_translation(val, lang_id) return val.value commands = { # Special characters / glyphs '': {'unicode': r'\0D', 'ascii': r'\0D'}, '{': {'unicode': r'{', 'ascii': r'{' }, 'NBSP': {'unicode': r'\U00A0'}, # character A0 is used as up arrow in TTD, so don't use ASCII here. 'COPYRIGHT': {'unicode': r'\U00A9', 'ascii': r'\A9'}, 'TRAIN': {'unicode': r'\UE0B4', 'ascii': r'\B4'}, 'LORRY': {'unicode': r'\UE0B5', 'ascii': r'\B5'}, 'BUS': {'unicode': r'\UE0B6', 'ascii': r'\B6'}, 'PLANE': {'unicode': r'\UE0B7', 'ascii': r'\B7'}, 'SHIP': {'unicode': r'\UE0B8', 'ascii': r'\B8'}, # Change the font size. 'TINYFONT': {'unicode': r'\0E', 'ascii': r'\0E'}, 'BIGFONT': {'unicode': r'\0F', 'ascii': r'\0F'}, 'COMMA': {'unicode': r'\UE07B', 'ascii': r'\7B', 'size': 4, 'parse': com_parse_comma}, 'SIGNED_WORD': {'unicode': r'\UE07C', 'ascii': r'\7C', 'size': 2, 'parse': com_parse_comma}, 'UNSIGNED_WORD': {'unicode': r'\UE07E', 'ascii': r'\7E', 'size': 2, 'parse': com_parse_comma}, 'CURRENCY': {'unicode': r'\UE07F', 'ascii': r'\7F', 'size': 4}, 'STRING': {'unicode': r'\UE080', 'ascii': r'\80', 'allow_case': True, 'size': 2, 'parse': com_parse_string}, 'DATE1920_LONG': {'unicode': r'\UE082', 'ascii': r'\82', 'size': 2}, 'DATE1920_SHORT': {'unicode': r'\UE083', 'ascii': r'\83', 'size': 2}, 'VELOCITY': {'unicode': r'\UE084', 'ascii': r'\84', 'size': 2}, 'SKIP': {'unicode': r'\UE085', 'ascii': r'\85', 'size': 2}, 'VOLUME': {'unicode': r'\UE087', 'ascii': r'\87', 'size': 2}, 'HEX': {'unicode': r'\UE09A\08', 'ascii': r'\9A\08', 'size': 4, 'parse': com_parse_hex}, 'STATION': {'unicode': r'\UE09A\0C', 'ascii': r'\9A\0C', 'size': 2}, 'WEIGHT': {'unicode': r'\UE09A\0D', 'ascii': r'\9A\0D', 'size': 2}, 'DATE_LONG': {'unicode': r'\UE09A\16', 'ascii': r'\9A\16', 'size': 4}, 'DATE_SHORT': {'unicode': r'\UE09A\17', 'ascii': r'\9A\17', 'size': 4}, 'POWER': {'unicode': r'\UE09A\18', 'ascii': r'\9A\18', 'size': 2}, 'VOLUME_SHORT': {'unicode': r'\UE09A\19', 'ascii': r'\9A\19', 'size': 2}, 'WEIGHT_SHORT': {'unicode': r'\UE09A\1A', 'ascii': r'\9A\1A', 'size': 2}, # Colors 'BLUE': {'unicode': r'\UE088', 'ascii': r'\88'}, 'SILVER': {'unicode': r'\UE089', 'ascii': r'\89'}, 'GOLD': {'unicode': r'\UE08A', 'ascii': r'\8A'}, 'RED': {'unicode': r'\UE08B', 'ascii': r'\8B'}, 'PURPLE': {'unicode': r'\UE08C', 'ascii': r'\8C'}, 'LTBROWN': {'unicode': r'\UE08D', 'ascii': r'\8D'}, 'ORANGE': {'unicode': r'\UE08E', 'ascii': r'\8E'}, 'GREEN': {'unicode': r'\UE08F', 'ascii': r'\8F'}, 'YELLOW': {'unicode': r'\UE090', 'ascii': r'\90'}, 'DKGREEN': {'unicode': r'\UE091', 'ascii': r'\91'}, 'CREAM': {'unicode': r'\UE092', 'ascii': r'\92'}, 'BROWN': {'unicode': r'\UE093', 'ascii': r'\93'}, 'WHITE': {'unicode': r'\UE094', 'ascii': r'\94'}, 'LTBLUE': {'unicode': r'\UE095', 'ascii': r'\95'}, 'GRAY': {'unicode': r'\UE096', 'ascii': r'\96'}, 'DKBLUE': {'unicode': r'\UE097', 'ascii': r'\97'}, 'BLACK': {'unicode': r'\UE098', 'ascii': r'\98'}, # Deprecated string codes 'DWORD_S': {'unicode': r'\UE07B', 'ascii': r'\7B', 'deprecated': True, 'size': 4}, 'PARAM': {'unicode': r'\UE07B', 'ascii': r'\7B', 'deprecated': True, 'size': 4}, 'WORD_S': {'unicode': r'\UE07C', 'ascii': r'\7C', 'deprecated': True, 'size': 2}, 'BYTE_S': {'unicode': r'\UE07D', 'ascii': r'\7D', 'deprecated': True}, 'WORD_U': {'unicode': r'\UE07E', 'ascii': r'\7E', 'deprecated': True, 'size': 2}, 'POP_WORD': {'unicode': r'\UE085', 'ascii': r'\85', 'deprecated': True, 'size': 2}, 'CURRENCY_QWORD': {'unicode': r'\UE09A\01', 'ascii': r'\9A\01', 'deprecated': True}, 'PUSH_WORD': {'unicode': r'\UE09A\03', 'ascii': r'\9A\03', 'deprecated': True}, 'UNPRINT': {'unicode': r'\UE09A\04', 'ascii': r'\9A\04', 'deprecated': True}, 'BYTE_HEX': {'unicode': r'\UE09A\06', 'ascii': r'\9A\06', 'deprecated': True}, 'WORD_HEX': {'unicode': r'\UE09A\07', 'ascii': r'\9A\07', 'deprecated': True, 'size': 2}, 'DWORD_HEX': {'unicode': r'\UE09A\08', 'ascii': r'\9A\08', 'deprecated': True, 'size': 4}, 'QWORD_HEX': {'unicode': r'\UE09A\0B', 'ascii': r'\9A\0B', 'deprecated': True}, 'WORD_S_TONNES': {'unicode': r'\UE09A\0D', 'ascii': r'\9A\0D', 'deprecated': True, 'size': 2}, } special_commands = [ 'P', 'G', 'G=', ] def read_extra_commands(custom_tags_file): """ @param custom_tags_file: Filename of the custom tags file. @type custom_tags_file: C{str} """ if not os.access(custom_tags_file, os.R_OK): #Failed to open custom_tags.txt, ignore this return line_no = 0 for line in codecs.open(custom_tags_file, "r", "utf-8"): line_no += 1 line = line.strip() if len(line) == 0 or line[0] == "#": pass else: i = line.find(':') if i == -1: raise generic.ScriptError("Line has no ':' delimiter", generic.LinePosition(custom_tags_file, line_no)) name = line[:i].strip() value = line[i+1:] if name in commands: generic.print_warning('Warning: overwriting existing tag "' + name + '"') commands[name] = {'unicode': value} if is_ascii_string(value): commands[name]['ascii'] = value class StringCommand(object): def __init__(self, name, str_pos): assert name in commands or name in special_commands self.name = name self.case = None self.arguments = [] self.offset = None self.str_pos = str_pos def set_arguments(self, arg_string): start = -1 cur = 0 quoted = False whitespace = " \t" while cur < len(arg_string): if start != -1: if (quoted and arg_string[cur] == '"') or (not quoted and arg_string[cur] in whitespace): if not quoted and self.offset is None and len(self.arguments) == 0 and isint(arg_string[start:cur]) and self.name in ('P', 'G'): self.offset = int(arg_string[start:cur]) else: self.arguments.append(arg_string[start:cur]) start = -1 elif arg_string[cur] not in whitespace: quoted = arg_string[cur] == '"' start = cur + 1 if quoted else cur cur += 1 if start != -1 and not quoted: self.arguments.append(arg_string[start:]) start = -1 return start == -1 def validate_arguments(self, lang, pos): if lang.langid == DEFAULT_LANGUAGE: return if self.name == 'P': if len(self.arguments) != lang.get_num_plurals(): raise generic.ScriptError("Invalid number of arguments to plural command, expected %d but got %d" % (lang.get_num_plurals(), len(self.arguments)), pos) elif self.name == 'G': if len(self.arguments) != len(lang.genders): raise generic.ScriptError("Invalid number of arguments to gender command, expected %d but got %d" % (len(lang.genders), len(self.arguments)), pos) elif self.name == 'G=': if len(self.arguments) != 1: raise generic.ScriptError("Invalid number of arguments to set-gender command, expected %d but got %d" % (1, len(self.arguments)), pos) elif len(self.arguments) != 0: raise generic.ScriptError("Unexpected arguments to command \"%s\"" % self.name, pos) def parse_string(self, str_type, lang, stack, static_args): if self.name in commands: if not self.is_important_command(): return commands[self.name][str_type] stack_pos = 0 for (pos, size) in stack: if pos == self.str_pos: break stack_pos += size self_size = commands[self.name]['size'] stack.remove((self.str_pos, self_size)) if self.str_pos < len(static_args): if 'parse' not in commands[self.name]: raise generic.ScriptError("Provided a static argument for string command '%s' which is invalid" % self.name) return commands[self.name]['parse'](static_args[self.str_pos], lang.langid) prefix = u'' suffix = u'' if self.case: prefix += STRING_SELECT_CASE[str_type] + '\\%02X' % self.case if stack_pos + self_size > 8: raise generic.ScriptError("Trying to read an argument from the stack without reading the arguments before") if self_size == 4 and stack_pos == 4: prefix += STRING_ROTATE[str_type] + STRING_ROTATE[str_type] elif self_size == 4 and stack_pos == 2: prefix += STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] elif self_size == 2 and stack_pos == 6: prefix += STRING_ROTATE[str_type] elif self_size == 2 and stack_pos == 4: prefix += STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] elif self_size == 2 and stack_pos == 2: prefix += STRING_PUSH_WORD[str_type] + STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] + STRING_SKIP[str_type] else: assert stack_pos == 0 return prefix + commands[self.name][str_type] + suffix assert self.name in special_commands # Create a local copy because we shouldn't modify the original offset = self.offset if offset is None: if not stack: raise generic.ScriptError("A plural or gender choice list {P} or {G} has to be followed by another string code or provide an offset") offset = stack[0][0] offset -= len(static_args) if self.name == 'P': if offset < 0: return self.arguments[lang.static_plural_form(static_args[offset]) - 1] ret = BEGIN_PLURAL_CHOICE_LIST[str_type] + '\\%02X' % (0x80 + offset) for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: ret += CHOICE_LIST_ITEM[str_type] + '\\%02X' % (idx + 1) ret += arg ret += CHOICE_LIST_END[str_type] return ret if self.name == 'G': if offset < 0: return self.arguments[lang.static_gender(static_args[offset]) - 1] ret = BEGIN_GENDER_CHOICE_LIST[str_type] + '\\%02X' % (0x80 + offset) for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: ret += CHOICE_LIST_ITEM[str_type] + '\\%02X' % (idx + 1) ret += arg ret += CHOICE_LIST_END[str_type] return ret def get_type(self): if self.name in commands: if 'ascii' in commands[self.name]: return 'ascii' else: return 'unicode' if self.name == 'P' or self.name == 'G': for arg in self.arguments: if not is_ascii_string(arg): return 'unicode' return 'ascii' def is_important_command(self): if self.name in special_commands: return False return 'size' in commands[self.name] def get_arg_size(self): return commands[self.name]['size'] # Characters that are valid in hex numbers VALID_HEX = "0123456789abcdefABCDEF" def is_valid_hex(string): return all(c in VALID_HEX for c in string) def validate_escapes(string, pos): """ Validate that all escapes (starting with a backslash) are correct. When an invalid escape is encountered, an error is thrown @param string: String to validate @type string: C{unicode} @param pos: Position information @type pos: L{Position} """ i = 0 while i < len(string): # find next '\' i = string.find('\\', i) if i == -1: break if i+1 >= len(string): raise generic.ScriptError("Unexpected end-of-line encountered after '\\'", pos) if string[i+1] in ('\\', '"'): i += 2 elif string[i+1] == 'U': if i+5 >= len(string) or not is_valid_hex(string[i+2:i+6]): raise generic.ScriptError("Expected 4 hexadecimal characters after '\\U'", pos) i += 6 else: if i+2 >= len(string) or not is_valid_hex(string[i+1:i+3]): raise generic.ScriptError("Expected 2 hexadecimal characters after '\\'", pos) i += 3 class NewGRFString(object): def __init__(self, string, lang, strip_choice_lists, pos): validate_escapes(string, pos) self.string = string self.cases = {} self.components = [] self.pos = pos parsed_string = None idx = 0 while idx < len(string): if string[idx] != '{': if parsed_string is None: parsed_string = u"" parsed_string += string[idx] else: if parsed_string is not None: self.components.append(parsed_string) parsed_string = None start = idx + 1 end = start cmd_pos = None if start >= len(string): raise generic.ScriptError("Expected '}' before end-of-line.", pos) if string[start].isdigit(): while end < len(string) and string[end].isdigit(): end += 1 if end == len(string) or string[end] != ':': raise generic.ScriptError("Error while parsing position part of string command", pos) cmd_pos = int(string[start:end]) start = end + 1 end = start #Read the command name while end < len(string) and string[end] not in '} =.': end += 1 command_name = string[start:end] if end < len(string) and string[end] == '=': command_name += '=' if command_name not in commands and command_name not in special_commands: raise generic.ScriptError("Undefined command \"%s\"" % command_name, pos) if command_name in commands and 'deprecated' in commands[command_name]: generic.print_warning("String code '%s' has been deprecated and will be removed soon" % command_name, pos) del commands[command_name]['deprecated'] # command = StringCommand(command_name, cmd_pos) if end >= len(string): raise generic.ScriptError("Missing '}' from command \"%s\"" % string[start:], pos) if string[end] == '.': if command_name not in commands or 'allow_case' not in commands[command_name]: raise generic.ScriptError("Command \"%s\" can't have a case" % command_name, pos) case_start = end + 1 end = case_start while end < len(string) and string[end] not in '} ': end += 1 case = string[case_start:end] if lang.cases is None or case not in lang.cases: raise generic.ScriptError("Invalid case-name \"%s\"" % case, pos) command.case = lang.cases[case] if string[end] != '}': command.argument_is_assigment = string[end] == '=' arg_start = end + 1 while True: end += 1 if end >= len(string): raise generic.ScriptError("Missing '}' from command \"%s\"" % string[start:], pos) if string[end] == '}': break if not command.set_arguments(string[arg_start:end]): raise generic.ScriptError("Missing '}' from command \"%s\"" % string[start:], pos) command.validate_arguments(lang, pos) if command_name == 'G=' and self.components: raise generic.ScriptError("Set-gender command {G=} must be at the start of the string", pos) self.components.append(command) idx = end idx += 1 if parsed_string is not None: self.components.append(parsed_string) if len(self.components) > 0 and isinstance(self.components[0], StringCommand) and self.components[0].name == 'G=': self.gender = self.components[0].arguments[0] if self.gender not in lang.genders: raise generic.ScriptError("Invalid gender name '%s'" % self.gender, pos) self.components.pop(0) else: self.gender = None cmd_pos = 0 for cmd in self.components: if not (isinstance(cmd, StringCommand) and cmd.is_important_command()): continue if cmd.str_pos is None: cmd.str_pos = cmd_pos cmd_pos = cmd.str_pos + 1 def get_type(self): for comp in self.components: if isinstance(comp, StringCommand): if comp.get_type() == 'unicode': return 'unicode' else: if not is_ascii_string(comp): return 'unicode' for case in self.cases.values(): if case.get_type() == 'unicode': return 'unicode' return 'ascii' def remove_non_default_commands(self): i = 0 while i < len(self.components): comp = self.components[i] if isinstance(comp, StringCommand): if comp.name == 'P' or comp.name == 'G': self.components[i] = comp.arguments[-1] i += 1 def parse_string(self, str_type, lang, static_args): ret = "" stack = [(idx, size) for idx, size in enumerate(self.get_command_sizes())] for comp in self.components: if isinstance(comp, StringCommand): ret += comp.parse_string(str_type, lang, stack, static_args) else: ret += comp return ret def get_command_sizes(self): sizes = {} for cmd in self.components: if not (isinstance(cmd, StringCommand) and cmd.is_important_command()): continue if cmd.str_pos in sizes: raise generic.ScriptError("Two or more string commands are using the same argument", self.pos) sizes[cmd.str_pos] = cmd.get_arg_size() sizes_list = [] for idx in range(len(sizes)): if idx not in sizes: raise generic.ScriptError("String argument %d is not used" % idx, self.pos) sizes_list.append(sizes[idx]) return sizes_list def match_commands(self, other_string): return self.get_command_sizes() == other_string.get_command_sizes() def isint(x, base = 10): try: int(x, base) return True except ValueError: return False NUM_PLURAL_FORMS = 12 CHOICE_LIST_ITEM = {'unicode': r'\UE09A\10', 'ascii': r'\9A\10'} CHOICE_LIST_DEFAULT = {'unicode': r'\UE09A\11', 'ascii': r'\9A\11'} CHOICE_LIST_END = {'unicode': r'\UE09A\12', 'ascii': r'\9A\12'} BEGIN_GENDER_CHOICE_LIST = {'unicode': r'\UE09A\13', 'ascii': r'\9A\13'} BEGIN_CASE_CHOICE_LIST = {'unicode': r'\UE09A\14', 'ascii': r'\9A\14'} BEGIN_PLURAL_CHOICE_LIST = {'unicode': r'\UE09A\15', 'ascii': r'\9A\15'} SET_STRING_GENDER = {'unicode': r'\UE09A\0E', 'ascii': r'\9A\0E'} STRING_SKIP = {'unicode': r'\UE085', 'ascii': r'\85'} STRING_ROTATE = {'unicode': r'\UE086', 'ascii': r'\86'} STRING_PUSH_WORD = {'unicode': r'\UE09A\03\20\20', 'ascii': r'\9A\03\20\20'} STRING_SELECT_CASE = {'unicode': r'\UE09A\0F', 'ascii': r'\9A\0F'} class Language: def __init__(self): self.langid = None self.plural = None self.genders = None self.gender_map = {} self.cases = None self.case_map = {} self.strings = {} def get_num_plurals(self): if self.plural is None: return 0 num_plurals = { 0: 2, 1: 1, 2: 2, 3: 3, 4: 5, 5: 3, 6: 3, 7: 3, 8: 4, 9: 2, 10: 3, 11: 2, 12: 4, } return num_plurals[self.plural] def static_gender(self, expr): import nml.expression if isinstance(expr, nml.expression.StringLiteral): return len(self.genders) if not isinstance(expr, nml.expression.String): raise generic.ScriptError("{G} can only refer to a string argument") parsed = self.get_string(expr) if parsed.find(SET_STRING_GENDER['ascii']) == 0: return int(parsed[len(SET_STRING_GENDER['ascii']) + 1 : len(SET_STRING_GENDER['ascii']) + 3], 16) if parsed.find(SET_STRING_GENDER['unicode']) == 0: return int(parsed[len(SET_STRING_GENDER['unicode']) + 1 : len(SET_STRING_GENDER['unicode']) + 3], 16) return len(self.genders) def static_plural_form(self, expr): #Return values are the same as "Plural index" here: #http://newgrf-specs.tt-wiki.net/wiki/StringCodes#Using_plural_forms val = expr.reduce_constant().value if self.plural == 0: return 1 if val == 1 else 2 if self.plural == 1: return 1 if self.plural == 2: return 1 if val in (0, 1) else 2 if self.plural == 3: if val % 10 == 1 and val % 100 != 11: return 1 return 2 if val == 0 else 3 if self.plural == 4: if val == 1: return 1 if val == 2: return 2 if 3 <= val <= 6: return 3 if 7 <= val <= 10: return 4 return 5 if self.plural == 5: if val % 10 == 1 and val % 100 != 11: return 1 if 2 <= (val % 10) <= 9 and not 12 <= (val % 100) <= 19: return 2 return 3 if self.plural == 6: if val % 10 == 1 and val % 100 != 11: return 1 if 2 <= (val % 10) <= 4 and not 12 <= (val % 100) <= 14: return 2 return 3 if self.plural == 7: if val == 0: return 1 if 2 <= (val % 10) <= 4 and not 12 <= (val % 100) <= 14: return 2 return 3 if self.plural == 8: if val % 100 == 1: return 1 if val % 100 == 2: return 2 if val % 100 in (3, 4): return 3 return 4 if self.plural == 9: if val % 10 == 1 and val % 100 != 11: return 1 return 2 if self.plural == 10: if val == 1: return 1 if 2 <= val <= 4: return 2 return 3 if self.plural == 11: if val % 10 in (0, 1, 3, 6, 7, 8): return 1 return 2 if self.plural == 12: if val == 1: return 1 if val == 0 or 2 <= (val % 100) <= 10: return 2 if 11 <= (val % 100) <= 19: return 3 return 4 assert False, "Unknown plural type" def get_string(self, string): string_id = string.name.value assert isinstance(string_id, basestring) assert string_id in self.strings str_type = self.strings[string_id].get_type() parsed_string = "" if self.strings[string_id].gender is not None: parsed_string += SET_STRING_GENDER[str_type] + '\\%02X' % self.genders[self.strings[string_id].gender] if len(self.strings[string_id].cases) > 0: parsed_string += BEGIN_CASE_CHOICE_LIST[str_type] for case_name, case_string in self.strings[string_id].cases.iteritems(): case_id = self.cases[case_name] parsed_string += CHOICE_LIST_ITEM[str_type] + ('\\%02X' % case_id) + case_string.parse_string(str_type, self, string.params) parsed_string += CHOICE_LIST_DEFAULT[str_type] parsed_string += self.strings[string_id].parse_string(str_type, self, string.params) if len(self.strings[string_id].cases) > 0: parsed_string += CHOICE_LIST_END[str_type] return parsed_string def handle_pragma(self, line, pos): if line[:10] == "grflangid ": if self.langid is not None: raise generic.ScriptError("grflangid already set", pos) try: value = int(line[10:], 16) except ValueError: raise generic.ScriptError("Invalid grflangid", pos) if value < 0 or value >= 0x7F: raise generic.ScriptError("Invalid grflangid", pos) self.langid = value elif line[:7] == "plural ": if self.plural is not None: raise generic.ScriptError("plural form already set", pos) try: value = int(line[7:], 16) except ValueError: raise generic.ScriptError("Invalid plural form", pos) if value < 0 or value > NUM_PLURAL_FORMS: raise generic.ScriptError("Invalid plural form", pos) self.plural = value elif line[:7] == "gender ": if self.genders is not None: raise generic.ScriptError("Genders already defined", pos) self.genders = {} for idx, gender in enumerate(line[7:].split()): self.genders[gender] = idx + 1 self.gender_map[gender] = [] elif line[:11] == "map_gender ": if self.genders is None: raise generic.ScriptError("##map_gender is not allowed before ##gender", pos) genders = line[11:].split() if len(genders) != 2: raise generic.ScriptError("Invalid ##map_gender line", pos) if genders[0] not in self.genders: raise generic.ScriptError("Trying to map non-existing gender '%s'" % genders[0], pos) self.gender_map[genders[0]].append(genders[1]) elif line[:5] == "case ": if self.cases is not None: raise generic.ScriptError("Cases already defined", pos) self.cases = {} for idx, case in enumerate(line[5:].split()): self.cases[case] = idx + 1 self.case_map[case] = [] else: raise generic.ScriptError("Invalid pragma", pos) def handle_string(self, line, default, pos): if len(line) == 0: return if line[0] == '#': if len(line) > 2 and line[1] == '#' and line[2] != '#' and not default: self.handle_pragma(line[2:], pos) return s = line.find(':') if s == -1: raise generic.ScriptError("Line has no ':' delimiter", pos) string = line[:s].strip() value = line[s + 1:] case_pos = string.find('.') if case_pos == -1: case = None else: # Ignore cases for the default language if default: return case = string[case_pos + 1:] string = string[:case_pos] if string in self.strings and case is None: raise generic.ScriptError("String name \"%s\" is used multiple times" % string, pos) if default: self.strings[string] = NewGRFString(value, self, True, pos) self.strings[string].remove_non_default_commands() else: if string not in default_lang.strings: generic.print_warning("String name \"%s\" does not exist in master file" % string, pos) return newgrf_string = NewGRFString(value, self, False, pos) if not default_lang.strings[string].match_commands(newgrf_string): generic.print_warning("String commands don't match with english.lng", pos) return if case is None: self.strings[string] = newgrf_string else: if string not in self.strings: generic.print_warning("String with case used before the base string", pos) return if self.cases is None or case not in self.cases: generic.print_warning("Invalid case name \"%s\"" % case, pos) return if case in self.strings[string].cases: raise generic.ScriptError("String name \"%s.%s\" is used multiple times" % (string, case), pos) if newgrf_string.gender: generic.print_warning("Case-strings can't set the gender, only the base string can", pos) return self.strings[string].cases[case] = newgrf_string default_lang = Language() default_lang.langid = DEFAULT_LANGUAGE langs = [] def parse_file(filename, default): """ Read and parse a single language file. @param filename: The filename of the file to parse. @type filename: C{str} @param default: True iff this is the default language. @type default: C{bool} """ lang = Language() try: with codecs.open(filename, "r", "utf-8") as f: for idx, line in enumerate(f): pos = generic.LinePosition(filename, idx + 1) line = line.rstrip('\n\r').lstrip(u'\uFEFF') if default: default_lang.handle_string(line, True, pos) lang.handle_string(line, False, pos) except UnicodeDecodeError: if default: raise generic.ScriptError("The default language file (\"%s\") contains non-utf8 characters." % filename) generic.print_warning("Language file \"%s\" contains non-utf8 characters. Ignoring (part of) the contents" % filename) except generic.ScriptError, err: if default: raise generic.print_warning("Error in language file \"%s\": %s" % (filename, err)) else: if lang.langid is None: generic.print_warning("Language file \"%s\" does not contain a ##grflangid pragma" % filename) else: langs.append((lang.langid, lang)) def read_lang_files(lang_dir, default_lang_file): """ Read the language files containing the translations for string constants used in the NML specification. @param lang_dir: Name of the directory containing the language files. @type lang_dir: C{str} @param default_lang_file: Filename of the language file that has the default translation which will be used as fallback for other languages. @type default_lang_file: C{str} """ if not os.path.exists(lang_dir + os.sep + default_lang_file): generic.print_warning("Default language file \"%s\" doesn't exist" % (lang_dir + os.sep + default_lang_file)) return parse_file(lang_dir + os.sep + default_lang_file, True) for filename in glob.glob(lang_dir + os.sep + "*.lng"): if filename.endswith(default_lang_file): continue parse_file(filename, False) langs.sort() nml-0.2.4/nml/parser.py0000644000061700006170000006527712036626442015511 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, tokens, nmlop from nml.ast import assignment, basecost, cargotable, conditional, deactivate, disable_item, error, font, general, grf, item, loop, produce, railtypetable, replace, spriteblock, switch, townnames, snowline, skipall, tilelayout, alt_sprites, base_sprites, override, sort_vehicles from nml.actions import actionD, real_sprite import ply.yacc as yacc class NMLParser(object): def __init__(self): self.lexer = tokens.NMLLexer() self.lexer.build() self.tokens = self.lexer.tokens self.parser = yacc.yacc(debug = False, module = self) def parse(self, text): return self.parser.parse(text, lexer = self.lexer.lexer) #operator precedence (lower in the list = higher priority) precedence = ( ('left','COMMA'), ('right','TERNARY_OPEN','COLON'), ('left','LOGICAL_OR'), ('left','LOGICAL_AND'), ('left','OR'), ('left','XOR'), ('left','AND'), ('left','COMP_EQ','COMP_NEQ','COMP_LE','COMP_GE','COMP_LT','COMP_GT'), ('left','SHIFT_LEFT','SHIFT_RIGHT','SHIFTU_RIGHT'), ('left','PLUS','MINUS'), ('left','TIMES','DIVIDE','MODULO'), ('left','LOGICAL_NOT','BINARY_NOT'), ) def p_error(self, t): if t is None: raise generic.ScriptError('Syntax error, unexpected end-of-file') else: raise generic.ScriptError('Syntax error, unexpected token "%s"' % t.value, t.lineno) # # Main script blocks # def p_main_script(self, t): 'main_script : script' t[0] = general.MainScript(t[1]) def p_script(self, t): '''script : | script main_block''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_main_block(self, t): '''main_block : switch | random_switch | produce | spriteset | spritegroup | spritelayout | template_declaration | tilelayout | town_names | cargotable | railtype | grf_block | param_assignment | skip_all | conditional | loop | item | property_block | graphics_block | liveryoverride_block | error_block | disable_item | deactivate | replace | replace_new | base_sprites | font_glyph | alt_sprites | snowline | engine_override | sort_vehicles | basecost''' t[0] = t[1] # # Expressions # def p_expression(self, t): '''expression : NUMBER | FLOAT | param | variable | ID | STRING_LITERAL | string''' t[0] = t[1] def p_parenthesed_expression(self, t): 'expression : LPAREN expression RPAREN' t[0] = t[2] def p_parameter(self, t): 'param : PARAMETER LBRACKET expression RBRACKET' t[0] = expression.Parameter(t[3], t.lineno(1), True) def p_parameter_other_grf(self, t): 'param : PARAMETER LBRACKET expression COMMA expression RBRACKET' t[0] = expression.OtherGRFParameter(t[3], t[5], t.lineno(1)) code_to_op = { '+' : nmlop.ADD, '-' : nmlop.SUB, '*' : nmlop.MUL, '/' : nmlop.DIV, '%' : nmlop.MOD, '&' : nmlop.AND, '|' : nmlop.OR, '^' : nmlop.XOR, '&&' : nmlop.AND, '||' : nmlop.OR, '==' : nmlop.CMP_EQ, '!=' : nmlop.CMP_NEQ, '<=' : nmlop.CMP_LE, '>=' : nmlop.CMP_GE, '<' : nmlop.CMP_LT, '>' : nmlop.CMP_GT, '<<' : nmlop.SHIFT_LEFT, '>>' : nmlop.SHIFT_RIGHT, '>>>': nmlop.SHIFTU_RIGHT, } def p_binop(self, t): '''expression : expression PLUS expression | expression MINUS expression | expression TIMES expression | expression DIVIDE expression | expression MODULO expression | expression AND expression | expression OR expression | expression XOR expression | expression SHIFT_LEFT expression | expression SHIFT_RIGHT expression | expression SHIFTU_RIGHT expression | expression COMP_EQ expression | expression COMP_NEQ expression | expression COMP_LE expression | expression COMP_GE expression | expression COMP_LT expression | expression COMP_GT expression''' t[0] = expression.BinOp(self.code_to_op[t[2]], t[1], t[3], t[1].pos) def p_binop_logical(self, t): '''expression : expression LOGICAL_AND expression | expression LOGICAL_OR expression''' t[0] = expression.BinOp(self.code_to_op[t[2]], expression.Boolean(t[1]), expression.Boolean(t[3]), t[1].pos) def p_logical_not(self, t): 'expression : LOGICAL_NOT expression' t[0] = expression.Not(expression.Boolean(t[2]), t.lineno(1)) def p_binary_not(self, t): 'expression : BINARY_NOT expression' t[0] = expression.BinNot(t[2], t.lineno(1)) def p_ternary_op(self, t): 'expression : expression TERNARY_OPEN expression COLON expression' t[0] = expression.TernaryOp(t[1], t[3], t[5], t[1].pos) def p_unary_minus(self, t): 'expression : MINUS expression' t[0] = expression.BinOp(self.code_to_op[t[1]], expression.ConstantNumeric(0), t[2], t.lineno(1)) def p_variable(self, t): 'variable : VARIABLE LBRACKET expression_list RBRACKET' t[0] = expression.Variable(*t[3]) t[0].pos = t.lineno(1) def p_function(self, t): 'expression : ID LPAREN expression_list RPAREN' t[0] = expression.FunctionCall(t[1], t[3], t[1].pos) def p_array(self, t): 'expression : LBRACKET expression_list RBRACKET' t[0] = expression.Array(t[2], t.lineno(1)) # # Commonly used non-terminals that are not expressions # def p_assignment_list(self, t): '''assignment_list : assignment | param_desc | assignment_list assignment | assignment_list param_desc''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_assignment(self, t): 'assignment : ID COLON expression SEMICOLON' t[0] = assignment.Assignment(t[1], t[3], t[1].pos) def p_param_desc(self, t): '''param_desc : PARAMETER expression LBRACE setting_list RBRACE | PARAMETER LBRACE setting_list RBRACE''' if len(t) == 5: t[0] = grf.ParameterDescription(t[3]) else: t[0] = grf.ParameterDescription(t[4], t[2]) def p_setting_list(self, t): '''setting_list : setting | setting_list setting''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_setting(self, t): 'setting : ID LBRACE setting_value_list RBRACE' t[0] = grf.ParameterSetting(t[1], t[3]) def p_setting_value_list(self, t): '''setting_value_list : setting_value | setting_value_list setting_value''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_setting_value(self, t): 'setting_value : assignment' t[0] = t[1] def p_names_setting_value(self, t): 'setting_value : ID COLON LBRACE name_string_list RBRACE SEMICOLON' t[0] = assignment.Assignment(t[1], t[4], t[1].pos) def p_name_string_list(self, t): '''name_string_list : name_string_item | name_string_list name_string_item''' if len(t) == 2: t[0] = expression.Array([t[1]], t[1].pos) else: t[0] = expression.Array(t[1].values + [t[2]], t[1].pos) def p_name_string_item(self, t): 'name_string_item : expression COLON string SEMICOLON' t[0] = assignment.Assignment(t[1], t[3], t[1].pos) def p_string(self, t): 'string : STRING LPAREN expression_list RPAREN' t[0] = expression.String(t[3], t.lineno(1)) def p_non_empty_expression_list(self, t): '''non_empty_expression_list : expression | non_empty_expression_list COMMA expression''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_expression_list(self, t): '''expression_list : | non_empty_expression_list | non_empty_expression_list COMMA''' t[0] = [] if len(t) == 1 else t[1] def p_non_empty_id_list(self, t): '''non_empty_id_list : ID | non_empty_id_list COMMA ID''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_id_list(self, t): '''id_list : | non_empty_id_list | non_empty_id_list COMMA''' t[0] = [] if len(t) == 1 else t[1] def p_generic_assignment(self, t): 'generic_assignment : expression COLON expression SEMICOLON' t[0] = assignment.Assignment(t[1], t[3], t.lineno(1)) def p_generic_assignment_list(self, t): '''generic_assignment_list : | generic_assignment_list generic_assignment''' t[0] = [] if len(t) == 1 else t[1] + [t[2]] # # Item blocks # def p_item(self, t): 'item : ITEM LPAREN expression_list RPAREN LBRACE script RBRACE' t[0] = item.Item(t[3], t[6], t.lineno(1)) def p_property_block(self, t): 'property_block : PROPERTY LBRACE property_list RBRACE' t[0] = item.PropertyBlock(t[3], t.lineno(1)) def p_property_list(self, t): '''property_list : property_assignment | property_list property_assignment''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_property_assignment(self, t): '''property_assignment : ID COLON expression SEMICOLON | ID COLON expression UNIT SEMICOLON | NUMBER COLON expression SEMICOLON | NUMBER COLON expression UNIT SEMICOLON''' name = t[1] unit = None if len(t) == 5 else item.Unit(t[4]) t[0] = item.Property(name, t[3], unit, t.lineno(1)) def p_graphics_block(self, t): 'graphics_block : GRAPHICS LBRACE graphics_list RBRACE' t[0] = item.GraphicsBlock(t[3][0], t[3][1], t.lineno(1)) def p_liveryoverride_block(self, t): 'liveryoverride_block : LIVERYOVERRIDE LPAREN expression RPAREN LBRACE graphics_list RBRACE' t[0] = item.LiveryOverride(t[3], item.GraphicsBlock(t[6][0], t[6][1], t.lineno(1)), t.lineno(1)) def p_graphics_list(self, t): '''graphics_list : graphics_assignment_list | graphics_assignment_list expression SEMICOLON | expression SEMICOLON''' # Save graphics block as a tuple, we need to add position info later if len(t) == 2: t[0] = (t[1], None) elif len(t) == 4: t[0] = (t[1], t[2]) else: t[0] = ([], t[1]) def p_graphics_assignment(self, t): 'graphics_assignment : expression COLON switch_value' t[0] = item.GraphicsDefinition(t[1], t[3]) def p_graphics_assignment_list(self, t): '''graphics_assignment_list : graphics_assignment | graphics_assignment_list graphics_assignment''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] # # Program flow control (if/else/while) # def p_conditional(self, t): '''conditional : if_else_parts | if_else_parts else_block''' parts = t[1] if len(t) > 2: parts.append(t[2]) t[0] = conditional.ConditionalList(parts) def p_else_block(self, t): 'else_block : ELSE LBRACE script RBRACE' t[0] = conditional.Conditional(None, t[3], t.lineno(1)) def p_if_else_parts(self, t): '''if_else_parts : IF LPAREN expression RPAREN LBRACE script RBRACE | if_else_parts ELSE IF LPAREN expression RPAREN LBRACE script RBRACE''' if len(t) == 8: t[0] = [conditional.Conditional(t[3], t[6], t.lineno(1))] else: t[0] = t[1] + [conditional.Conditional(t[5], t[8], t.lineno(2))] def p_loop(self, t): 'loop : WHILE LPAREN expression RPAREN LBRACE script RBRACE' t[0] = loop.Loop(t[3], t[6], t.lineno(1)) # # (Random) Switch block # def p_switch(self, t): 'switch : SWITCH LPAREN expression_list RPAREN LBRACE switch_body RBRACE' t[0] = switch.Switch(t[3], t[6], t.lineno(1)) def p_switch_body(self, t): 'switch_body : switch_ranges switch_value' t[0] = switch.SwitchBody(t[1], t[2]) def p_switch_ranges(self, t): '''switch_ranges : | switch_ranges expression COLON switch_value | switch_ranges expression UNIT COLON switch_value | switch_ranges expression RANGE expression COLON switch_value | switch_ranges expression RANGE expression UNIT COLON switch_value''' if len(t) == 1: t[0] = [] elif len(t) == 5: t[0] = t[1] + [switch.SwitchRange(t[2], t[2], t[4])] elif len(t) == 6: t[0] = t[1] + [switch.SwitchRange(t[2], t[2], t[5], t[3])] elif len(t) == 7: t[0] = t[1] + [switch.SwitchRange(t[2], t[4], t[6])] else: t[0] = t[1] + [switch.SwitchRange(t[2], t[4], t[7], t[5])] def p_switch_value(self, t): '''switch_value : RETURN expression SEMICOLON | RETURN SEMICOLON | expression SEMICOLON''' if len(t) == 4: t[0] = t[2] elif t[1] == 'return': t[0] = None else: t[0] = t[1] def p_random_switch(self, t): 'random_switch : RANDOMSWITCH LPAREN expression_list RPAREN LBRACE random_body RBRACE' t[0] = switch.RandomSwitch(t[3], t[6], t.lineno(1)) def p_random_body(self, t): '''random_body : | random_body expression COLON switch_value''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [switch.RandomChoice(t[2], t[4])] def p_produce(self, t): 'produce : PRODUCE LPAREN expression_list RPAREN SEMICOLON' t[0] = produce.Produce(t[3], t.lineno(1)) # # Real sprites and related stuff # def p_real_sprite(self, t): '''real_sprite : LBRACKET expression_list RBRACKET | ID COLON LBRACKET expression_list RBRACKET''' if len(t) == 4: t[0] = real_sprite.RealSprite(t[2]) else: t[0] = real_sprite.RealSprite(t[4], t[1]) def p_recolour_assignment_list(self, t): '''recolour_assignment_list : | recolour_assignment_list recolour_assignment''' t[0] = [] if len(t) == 1 else t[1] + [t[2]] def p_recolour_assignment_1(self, t): 'recolour_assignment : expression COLON expression SEMICOLON' t[0] = assignment.Assignment(assignment.Range(t[1], None), assignment.Range(t[3], None), t[1].pos) def p_recolour_assignment_2(self, t): 'recolour_assignment : expression RANGE expression COLON expression RANGE expression SEMICOLON' t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], t[7]), t[1].pos) def p_recolour_assignment_3(self, t): 'recolour_assignment : expression RANGE expression COLON expression SEMICOLON' t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], None), t[1].pos) def p_recolour_sprite(self, t): 'real_sprite : RECOLOUR_SPRITE LBRACE recolour_assignment_list RBRACE' t[0] = real_sprite.RecolourSprite(t[3]) def p_template_declaration(self, t): 'template_declaration : TEMPLATE ID LPAREN id_list RPAREN LBRACE spriteset_contents RBRACE' t[0] = spriteblock.TemplateDeclaration(t[2], t[4], t[7], t.lineno(1)) def p_template_usage(self, t): '''template_usage : ID LPAREN expression_list RPAREN | ID COLON ID LPAREN expression_list RPAREN''' if len(t) == 5: t[0] = real_sprite.TemplateUsage(t[1], t[3], None, t.lineno(1)) else: t[0] = real_sprite.TemplateUsage(t[3], t[5], t[1], t.lineno(1)) def p_spriteset_contents(self, t): '''spriteset_contents : real_sprite | template_usage | spriteset_contents real_sprite | spriteset_contents template_usage''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_replace(self, t): '''replace : REPLACESPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | REPLACESPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = replace.ReplaceSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = replace.ReplaceSprite(t[3], t[6], None, t.lineno(1)) def p_replace_new(self, t): '''replace_new : REPLACENEWSPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | REPLACENEWSPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = replace.ReplaceNewSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = replace.ReplaceNewSprite(t[3], t[6], None, t.lineno(1)) def p_base_sprites(self, t): '''base_sprites : BASE_SPRITES LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | BASE_SPRITES ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = base_sprites.BaseSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = base_sprites.BaseSprite(t[3], t[6], None, t.lineno(1)) def p_font_glyph(self, t): '''font_glyph : FONTGLYPH LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | FONTGLYPH ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = font.FontGlyphBlock(t[4], t[7], t[2], t.lineno(1)) else: t[0] = font.FontGlyphBlock(t[3], t[6], None, t.lineno(1)) def p_alt_sprites(self, t): 'alt_sprites : ALT_SPRITES LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE' t[0] = alt_sprites.AltSpritesBlock(t[3], t[6], t.lineno(1)) # # Sprite sets/groups and such # def p_spriteset(self, t): 'spriteset : SPRITESET LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE' t[0] = spriteblock.SpriteSet(t[3], t[6], t.lineno(1)) def p_spritegroup_normal(self, t): 'spritegroup : SPRITEGROUP ID LBRACE spriteview_list RBRACE' t[0] = spriteblock.SpriteGroup(t[2], t[4], t.lineno(1)) def p_spritelayout(self, t): '''spritelayout : SPRITELAYOUT ID LBRACE layout_sprite_list RBRACE | SPRITELAYOUT ID LPAREN id_list RPAREN LBRACE layout_sprite_list RBRACE''' if len(t) == 6: t[0] = spriteblock.SpriteLayout(t[2], [], t[4], t.lineno(1)) else: t[0] = spriteblock.SpriteLayout(t[2], t[4], t[7], t.lineno(1)) def p_spriteview_list(self, t): '''spriteview_list : | spriteview_list spriteview''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_spriteview(self, t): '''spriteview : ID COLON LBRACKET expression_list RBRACKET SEMICOLON | ID COLON expression SEMICOLON''' if len(t) == 7: t[0] = spriteblock.SpriteView(t[1], t[4], t.lineno(1)) else: t[0] = spriteblock.SpriteView(t[1], [t[3]], t.lineno(1)) def p_layout_sprite_list(self, t): '''layout_sprite_list : | layout_sprite_list layout_sprite''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_layout_sprite(self, t): 'layout_sprite : ID LBRACE layout_param_list RBRACE' t[0] = spriteblock.LayoutSprite(t[1], t[3], t.lineno(1)) def p_layout_param_list(self, t): '''layout_param_list : assignment | layout_param_list assignment''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] # # Town names # def p_town_names(self, t): '''town_names : TOWN_NAMES LPAREN expression RPAREN LBRACE town_names_param_list RBRACE | TOWN_NAMES LBRACE town_names_param_list RBRACE''' if len(t) == 8: t[0] = townnames.TownNames(t[3], t[6], t.lineno(1)) else: t[0] = townnames.TownNames(None, t[3], t.lineno(1)) def p_town_names_param_list(self, t): '''town_names_param_list : town_names_param | town_names_param_list town_names_param''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_town_names_param(self, t): '''town_names_param : ID COLON string SEMICOLON | LBRACE town_names_part_list RBRACE | LBRACE town_names_part_list COMMA RBRACE''' if t[1] != '{': t[0] = townnames.TownNamesParam(t[1], t[3], t.lineno(1)) else: t[0] = townnames.TownNamesPart(t[2], t.lineno(1)) def p_town_names_part_list(self, t): '''town_names_part_list : town_names_part | town_names_part_list COMMA town_names_part''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_town_names_part(self, t): '''town_names_part : TOWN_NAMES LPAREN expression COMMA expression RPAREN | ID LPAREN STRING_LITERAL COMMA expression RPAREN''' if t[1] == 'town_names': t[0] = townnames.TownNamesEntryDefinition(t[3], t[5], t.lineno(1)) else: t[0] = townnames.TownNamesEntryText(t[1], t[3], t[5], t.lineno(1)) # # Snow line # def p_snowline(self, t): 'snowline : SNOWLINE LPAREN ID RPAREN LBRACE generic_assignment_list RBRACE' t[0] = snowline.Snowline(t[3], t[6], t.lineno(1)) # # Various misc. main script blocks that don't belong anywhere else # def p_param_assignment(self, t): 'param_assignment : expression EQ expression SEMICOLON' t[0] = actionD.ParameterAssignment(t[1], t[3]) def p_error_block(self, t): 'error_block : ERROR LPAREN expression_list RPAREN SEMICOLON' t[0] = error.Error(t[3], t.lineno(1)) def p_disable_item(self, t): 'disable_item : DISABLE_ITEM LPAREN expression_list RPAREN SEMICOLON' t[0] = disable_item.DisableItem(t[3], t.lineno(1)) def p_cargotable(self, t): '''cargotable : CARGOTABLE LBRACE cargotable_list RBRACE | CARGOTABLE LBRACE cargotable_list COMMA RBRACE''' t[0] = cargotable.CargoTable(t[3], t.lineno(1)) def p_cargotable_list(self, t): '''cargotable_list : ID | STRING_LITERAL | cargotable_list COMMA ID | cargotable_list COMMA STRING_LITERAL''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_railtypetable(self, t): '''railtype : RAILTYPETABLE LBRACE railtypetable_list RBRACE | RAILTYPETABLE LBRACE railtypetable_list COMMA RBRACE''' t[0] = railtypetable.RailtypeTable(t[3], t.lineno(1)) def p_railtypetable_list(self, t): '''railtypetable_list : railtypetable_item | railtypetable_list COMMA railtypetable_item''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_railtypetable_item(self, t): '''railtypetable_item : ID | STRING_LITERAL | ID COLON LBRACKET expression_list RBRACKET''' if len(t) == 2: t[0] = t[1] else: t[0] = assignment.Assignment(t[1], t[4], t[1].pos) def p_basecost(self, t): 'basecost : BASECOST LBRACE generic_assignment_list RBRACE' t[0] = basecost.BaseCost(t[3], t.lineno(1)) def p_deactivate(self, t): 'deactivate : DEACTIVATE LPAREN expression_list RPAREN SEMICOLON' t[0] = deactivate.DeactivateBlock(t[3], t.lineno(1)) def p_grf_block(self, t): 'grf_block : GRF LBRACE assignment_list RBRACE' t[0] = grf.GRF(t[3], t.lineno(1)) def p_skip_all(self, t): 'skip_all : SKIP_ALL SEMICOLON' t[0] = skipall.SkipAll(t.lineno(1)) def p_engine_override(self, t): 'engine_override : ENGINE_OVERRIDE LPAREN expression_list RPAREN SEMICOLON' t[0] = override.EngineOverride(t[3], t.lineno(1)) def p_sort_vehicles(self, t): 'sort_vehicles : SORT_VEHICLES LPAREN expression_list RPAREN SEMICOLON' t[0] = sort_vehicles.SortVehicles(t[3], t.lineno(1)) def p_tilelayout(self, t): 'tilelayout : TILELAYOUT ID LBRACE tilelayout_list RBRACE' t[0] = tilelayout.TileLayout(t[2], t[4], t.lineno(1)) def p_tilelayout_list(self, t): '''tilelayout_list : tilelayout_item | tilelayout_list tilelayout_item''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_tilelayout_item_tile(self, t): 'tilelayout_item : expression COMMA expression COLON expression SEMICOLON' t[0] = tilelayout.LayoutTile(t[1], t[3], t[5]) def p_tilelayout_item_prop(self, t): 'tilelayout_item : assignment' t[0] = t[1] nml-0.2.4/nml/output_grf.py0000644000061700006170000002654312036626442016404 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import os from nml import generic, palette, output_base, lz77, grfstrings from nml.actions.real_sprite import palmap_w2d try: import Image except ImportError: pass class OutputGRF(output_base.BinaryOutputBase): def __init__(self, filename, compress_grf, crop_sprites): output_base.BinaryOutputBase.__init__(self, filename) self.compress_grf = compress_grf self.crop_sprites = crop_sprites def open_file(self): return open(self.filename, 'wb') def pre_close(self): output_base.BinaryOutputBase.pre_close(self) #terminate with 6 zero bytes (zero-size sprite + checksum) i = 0 while i < 6: self.wb(0) i += 1 def wb(self, byte): self.file.write(chr(byte)) def print_byte(self, value): value = self.prepare_byte(value) self.wb(value) def print_bytex(self, value, pretty_print = None): self.print_byte(value) def print_word(self, value): value = self.prepare_word(value) self.wb(value & 0xFF) self.wb(value >> 8) def print_wordx(self, value): self.print_word(value) def print_dword(self, value): value = self.prepare_dword(value) self.wb(value & 0xFF) self.wb((value >> 8) & 0xFF) self.wb((value >> 16) & 0xFF) self.wb(value >> 24) def print_dwordx(self, value): self.print_dword(value) def _print_utf8(self, char): for c in unichr(char).encode('utf8'): self.print_byte(ord(c)) def print_string(self, value, final_zero = True, force_ascii = False): if not grfstrings.is_ascii_string(value): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") self.print_byte(0xC3) self.print_byte(0x9E) i = 0 while i < len(value): if value[i] == '\\': if value[i+1] in ('\\', 'n', '"'): self.print_byte(ord(value[i+1])) i += 2 elif value[i+1] == 'U': self._print_utf8(int(value[i+2:i+6], 16)) i += 6 else: self.print_byte(int(value[i+1:i+3], 16)) i += 3 else: self._print_utf8(ord(value[i])) i += 1 if final_zero: self.print_byte(0) def newline(self, msg = "", prefix = "\t"): pass def comment(self, msg): pass def start_sprite(self, size, type = 0xFF): #The compression byte (=type) is counted when *not* 0xFF size += (type != 0xFF) output_base.BinaryOutputBase.start_sprite(self, size + 2) self.print_word(size) self.print_byte(type) if type == 0xFF: self._byte_count -= 1 def print_sprite(self, sprite_info): if not os.path.exists(sprite_info.file.value): raise generic.ImageError("File doesn't exist", sprite_info.file.value) im = Image.open(sprite_info.file.value) if im.mode != "P": raise generic.ImageError("image does not have a palette", sprite_info.file.value) im_pal = palette.validate_palette(im, sprite_info.file.value) x = sprite_info.xpos.value y = sprite_info.ypos.value size_x = sprite_info.xsize.value size_y = sprite_info.ysize.value (im_width, im_height) = im.size if x + size_x > im_width or y + size_y > im_height: raise generic.ScriptError("Read beyond bounds of image file '%s'" % sprite_info.file.value, sprite_info.file.pos) sprite = im.crop((x, y, x + size_x, y + size_y)) # Check for white pixels; those that cause "artefacts" when shading white_pixels = 0 for p in sprite.getdata(): if p == 255: white_pixels += 1 if white_pixels != 0: pixels = sprite.size[0] * sprite.size[1] image_pos = generic.PixelPosition(sprite_info.file.value, x, y) generic.print_warning("%s: %i of %i pixels (%i%%) are pure white" % (str(image_pos), white_pixels, pixels, white_pixels * 100 / pixels), sprite_info.file.pos) self.wsprite(sprite, sprite_info.xrel.value, sprite_info.yrel.value, sprite_info.compression.value, im_pal) def print_empty_realsprite(self): self.start_sprite(1) self.print_byte(0) self.end_sprite() def wsprite_header(self, sprite, size, xoffset, yoffset, compression): size_x, size_y = sprite.size self.start_sprite(size + 7, compression) self.print_byte(size_y) self.print_word(size_x) self.print_word(xoffset) self.print_word(yoffset) def fakecompress(self, data): i = 0 output = "" while i < len(data): l = min(len(data) - i, 127) output += chr(l) while l > 0: output += data[i] i += 1 l -= 1 return output def sprite_compress(self, data): data_str = ''.join(chr(c) for c in data) if self.compress_grf: lz = lz77.LZ77(data_str) stream = lz.encode() else: stream = self.fakecompress(data_str) return stream def wsprite_encoderegular(self, sprite, data, data_len, xoffset, yoffset, compression): self.wsprite_header(sprite, data_len, xoffset, yoffset, compression) for c in data: self.print_byte(ord(c)) #make up for the difference in byte count self._byte_count += data_len - len(data) self.end_sprite() def sprite_encode_tile(self, sprite, data): size_x, size_y = sprite.size if size_y > 255: raise generic.ScriptError("sprites higher than 255px are not supported") data_output = [] offsets = size_y * [0] for y in range(size_y): offsets[y] = len(data_output) + 2 * size_y row_data = data[y*size_x : (y+1)*size_x] last = size_x - 1 while last >= 0 and row_data[last] == 0: last -= 1 if last == -1: data_output += [0x80, 0] continue x1 = 0 while x1 < size_x and row_data[x1] == 0: x1 += 1 if x1 == size_x: # Completely transparant line data_output.append(0) data_output.append(0) continue x2 = size_x while row_data[x2 - 1] == 0: x2 -= 1 # Chunk can start maximu at 0xFF and has maximum width of 0x7F if x2 - 0xFF > 0x7F: return None if x2 - x1 > 0x7F: #too large to fit in one chunk, so split it up. data_output.append(0x7F) data_output.append(x1) data_output += row_data[x1 : x1 + 0x7F] x1 += 0x7F data_output.append((x2 - x1) | 0x80) data_output.append(x1) data_output += row_data[x1 : x2] output = [] for offset in offsets: output.append(offset & 0xFF) output.append(offset >> 8) output += data_output return output def crop_sprite(self, sprite, xoffset, yoffset): data = list(sprite.getdata()) size_x, size_y = sprite.size #Crop the top of the sprite y = 0 while y < size_y: x = 0 while x < size_x: if data[y * size_x + x] != 0: break x += 1 if x != size_x: break y += 1 if y != 0: yoffset += y sprite = sprite.crop((0, y, size_x, size_y)) data = list(sprite.getdata()) size_y -= y #Crop the bottom of the sprite y = size_y - 1 while y >= 0: x = 0 while x < size_x: if data[y * size_x + x] != 0: break x += 1 if x != size_x: break y -= 1 if y != size_y - 1: sprite = sprite.crop((0, 0, size_x, y + 1)) data = list(sprite.getdata()) size_y = y + 1 #Crop the left of the sprite x = 0 while x < size_x: y = 0 while y < size_y: if data[y * size_x + x] != 0: break y += 1 if y != size_y: break x += 1 if x != 0: xoffset += x sprite = sprite.crop((x, 0, size_x, size_y)) data = list(sprite.getdata()) size_x -= x #Crop the right of the sprite x = size_x - 1 while x >= 0: y = 0 while y < size_y: if data[y * size_x + x] != 0: break y += 1 if y != size_y: break x -= 1 if x != size_x - 1: sprite = sprite.crop((0, 0, x + 1, size_y)) return (sprite, xoffset, yoffset) def wsprite(self, sprite, xoffset, yoffset, compression, orig_pal): if self.crop_sprites and (compression & 0x40 == 0): all_blue = True for p in sprite.getdata(): if p != 0: all_blue = False break if all_blue: sprite = sprite.crop((0, 0, 1, 1)) xoffset = 0 yoffset = 0 else: sprite, xoffset, yoffset = self.crop_sprite(sprite, xoffset, yoffset) data = list(sprite.getdata()) if orig_pal == "WIN": if self.palette == "DOS": data = [palmap_w2d[x] for x in data] compressed_data = self.sprite_compress(data) data_len = len(data) tile_data = self.sprite_encode_tile(sprite, data) if tile_data is not None: tile_compressed_data = self.sprite_compress(tile_data) if len(tile_compressed_data) < len(compressed_data): compression |= 8 compressed_data = tile_compressed_data data_len = len(tile_data) self.wsprite_encoderegular(sprite, compressed_data, data_len, xoffset, yoffset, compression) def print_named_filedata(self, filename): name = os.path.split(filename)[1] size = os.path.getsize(filename) total = 2 + len(name) + 1 + size self.start_sprite(total) self.print_bytex(0xff) self.print_bytex(len(name)) self.print_string(name, force_ascii = True, final_zero = True) # ASCII filenames seems sufficient. fp = open(filename, 'rb') while True: data = fp.read(1024) if len(data) == 0: break for d in data: self.print_bytex(ord(d)) fp.close() self.end_sprite() nml-0.2.4/nml/unit.py0000644000061700006170000000417412036626442015161 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" units = {} units['nfo'] = {'type': 'nfo', 'convert': 1, 'ottd_mul': 1, 'ottd_shift': 0} #don't convert, take value literal # Conversion factor works like this: # 1 reference_unit = convert other_unit # So nfo_value = property_value / convert * property_specific_conversion_factor # ottd_mul and ottd_shift are the values taken from OpenTTD's src/strings.cpp and # are used to calculate the displayed value by OpenTTD. If possible, adjust_values # increases or decreases the NFO value so that the desired display value is actually # achieved #Speed (reference: m/s) units['mph'] = {'type': 'speed', 'convert': 2.236936, 'ottd_mul': 1, 'ottd_shift': 0} units['km/h'] = {'type': 'speed', 'convert': 3.6, 'ottd_mul': 103, 'ottd_shift': 6} units['m/s'] = {'type': 'speed', 'convert': 1, 'ottd_mul': 1831, 'ottd_shift': 12} #Power (reference: hpI (imperial hp)) units['hp'] = {'type': 'power', 'convert': 1, 'ottd_mul': 1, 'ottd_shift': 0} # Default to imperial hp units['kW'] = {'type': 'power', 'convert': 0.745699, 'ottd_mul': 6109, 'ottd_shift': 13} units['hpM'] = {'type': 'power', 'convert': 1.013869, 'ottd_mul': 4153, 'ottd_shift': 12} units['hpI'] = {'type': 'power', 'convert': 1, 'ottd_mul': 1, 'ottd_shift': 0} #Weight (reference: ton) units['ton'] = {'type': 'weight', 'convert': 1, 'ottd_mul': 1, 'ottd_shift': 0} units['tons'] = {'type': 'weight', 'convert': 1, 'ottd_mul': 1, 'ottd_shift': 0} units['kg'] = {'type': 'weight', 'convert': 1000.0, 'ottd_mul': 1000, 'ottd_shift': 0} nml-0.2.4/nml/expression/0000755000061700006170000000000012036626604016021 5ustar abuildabuild00000000000000nml-0.2.4/nml/expression/special_parameter.py0000644000061700006170000000622412036626441022056 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from .base_expression import Expression class SpecialParameter(Expression): """ Class for handling special grf parameters. These can be assigned special, custom methods for reading / writing to them. @ivar name: Name of the parameter, for debugging purposes @type name: C{basestring} @ivar info: Information about the parameter @type info: C{dict} @ivar write_func: Function that will be called when the parameter is the target of an assignment Arguments: Dictionary with parameter information (self.info) Target expression to assign Position information Return value is a 2-tuple: Left side of the assignment (must be a parameter) Right side of the assignment (may be any expression) @type write_func: C{function} @ivar read_func: Function that will be called to read out the parameter value Arguments: Dictionary with parameter information (self.info) Position information Return value: Expression that should be evaluated to get the parameter value @type read_func: C{function} @ivar is_bool: Does read_func return a boolean value? @type is_bool: C{bool} """ def __init__(self, name, info, write_func, read_func, is_bool, pos = None): Expression.__init__(self, pos) self.name = name self.info = info self.write_func = write_func self.read_func = read_func self.is_bool = is_bool def debug_print(self, indentation): print indentation*' ' + "Special parameter '%s'" % self.name def __str__(self): return self.name def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def is_boolean(self): return self.is_bool def can_assign(self): return self.write_func is not None def to_assignment(self, expr): param, expr = self.write_func(self.info, expr, self.pos) param = param.reduce() expr = expr.reduce() return (param, expr) def to_reading(self): param = self.read_func(self.info, self.pos) param = param.reduce() return param def supported_by_actionD(self, raise_error): return True def supported_by_action2(self, raise_error): return True nml-0.2.4/nml/expression/identifier.py0000644000061700006170000000433112036626441020515 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression, ConstantNumeric from .string_literal import StringLiteral ignore_all_invalid_ids = False class Identifier(Expression): def __init__(self, value, pos = None): Expression.__init__(self, pos) self.value = value def debug_print(self, indentation): print indentation*' ' + 'ID: ' + self.value def __str__(self): return self.value def reduce(self, id_dicts = [], unknown_id_fatal = True, search_func_ptr = False): for id_dict in id_dicts: id_d, func = (id_dict, lambda x, pos: StringLiteral(x, pos) if isinstance(x, basestring) else ConstantNumeric(x, pos)) if not isinstance(id_dict, tuple) else id_dict if self.value in id_d: if search_func_ptr: # Do not reduce function pointers, since they have no (numerical) value return func(id_d[self.value], self.pos) else: return func(id_d[self.value], self.pos).reduce(id_dicts) if unknown_id_fatal and not ignore_all_invalid_ids: raise generic.ScriptError("Unrecognized identifier '" + self.value + "' encountered", self.pos) return self def supported_by_actionD(self, raise_error): if raise_error: raise generic.ScriptError("Unknown identifier '%s'" % self.value, self.pos) return False def __eq__(self, other): return other is not None and isinstance(other, Identifier) and self.value == other.value def __hash__(self): return hash(self.value) nml-0.2.4/nml/expression/parameter.py0000644000061700006170000001004712036626441020354 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, grfstrings from .base_expression import Type, Expression, ConstantNumeric from .string_literal import StringLiteral class Parameter(Expression): def __init__(self, num, pos = None, by_user = False): Expression.__init__(self, pos) self.num = num if by_user and isinstance(num, ConstantNumeric) and not (0 <= num.value <= 63): generic.print_warning("Accessing parameters out of the range 0..63 is not supported and may lead to unexpected behaviour.", pos) def debug_print(self, indentation): print indentation*' ' + 'Parameter:' self.num.debug_print(indentation + 2) def __str__(self): return 'param[%s]' % str(self.num) def reduce(self, id_dicts = [], unknown_id_fatal = True): num = self.num.reduce(id_dicts) if num.type() != Type.INTEGER: raise generic.ScriptError("Parameter number must be an integer.", num.pos) return Parameter(num, self.pos) def supported_by_action2(self, raise_error): supported = isinstance(self.num, ConstantNumeric) if not supported and raise_error: raise generic.ScriptError("Parameter acessess with non-constant numbers are not supported in a switch-block.", self.pos) return supported def supported_by_actionD(self, raise_error): return True def __eq__(self, other): return other is not None and isinstance(other, Parameter) and self.num == other.num def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.num,)) class OtherGRFParameter(Expression): def __init__(self, grfid, num, pos = None): Expression.__init__(self, pos) self.grfid = grfid self.num = num def debug_print(self, indentation): print indentation*' ' + 'OtherGRFParameter:' self.grfid.debug_print(indentation + 2) self.num.debug_print(indentation + 2) def __str__(self): return 'param[%s, %s]' % (str(self.grfid), str(self.num)) def reduce(self, id_dicts = [], unknown_id_fatal = True): grfid = self.grfid.reduce(id_dicts) #Test validity parse_string_to_dword(grfid) num = self.num.reduce(id_dicts) if num.type() != Type.INTEGER: raise generic.ScriptError("Parameter number must be an integer.", num.pos) return OtherGRFParameter(grfid, num, self.pos) def supported_by_action2(self, raise_error): if raise_error: raise generic.ScriptError("Reading parameters from another GRF is not supported in a switch-block.", self.pos) return False def supported_by_actionD(self, raise_error): return True def parse_string_to_dword(string): if not isinstance(string, StringLiteral) or grfstrings.get_string_size(string.value, False, True) != 4: raise generic.ScriptError("Expected a string literal of length 4", string.pos) pos = string.pos string = string.value bytes = [] i = 0 try: while len(bytes) < 4: if string[i] == '\\': bytes.append(int(string[i+1:i+3], 16)) i += 3 else: bytes.append(ord(string[i])) i += 1 except ValueError: raise generic.ScriptError("Cannot convert string to integer id", pos) return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24) nml-0.2.4/nml/expression/base_expression.py0000644000061700006170000001257112036626441021571 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class Type(object): """ Enum-type class of the various value types possible in NML """ INTEGER = 0 FLOAT = 1 STRING_LITERAL = 2 FUNCTION_PTR = 3 SPRITEGROUP_REF = 4 class Expression(object): """ Superclass for all expression classes. @ivar pos: Position of the data in the original file. @type pos: :L{Position} """ def __init__(self, pos): self.pos = pos def debug_print(self, indentation): """ Print all data with explanation of what it is to standard output. @param indentation: Indent all printed lines with at least C{indentation} spaces. """ raise NotImplementedError('debug_print must be implemented in expression-subclass %r' % type(self)) def __str__(self): """ Convert this expression to a string representing this expression in valid NML-code. @return: A string representation of this expression. """ raise NotImplementedError('__str__ must be implemented in expression-subclass %r' % type(self)) def reduce(self, id_dicts = [], unknown_id_fatal = True): """ Reduce this expression to the simplest representation possible. @param id_dicts: A list with dicts that are used to map identifiers to another (often numeric) representation. @param unknown_id_fatal: Is encountering an unknown identifier somewhere in this expression a fatal error? @return: A deep copy of this expression simplified as much as possible. """ raise NotImplementedError('reduce must be implemented in expression-subclass %r' % type(self)) def reduce_constant(self, id_dicts = []): """ Reduce this expression and make sure the result is a constant number. @param id_dicts: A list with dicts that are used to map identifiers to another (often numeric) representation. @return: A constant number that is the result of this expression. """ expr = self.reduce(id_dicts) if not isinstance(expr, ConstantNumeric): raise generic.ConstError(self.pos) return expr def supported_by_action2(self, raise_error): """ Check if this expression can be used inside a switch-block. @param raise_error: If true raise a scripterror instead of returning false. @return: True if this expression can be calculated by advanced varaction2. """ if raise_error: raise generic.ScriptError("This expression is not supported in a switch-block", self.pos) return False def supported_by_actionD(self, raise_error): """ Check if this expression can be used inside a parameter-assignment. @param raise_error: If true raise a scripterror instead of returning false. @return: True if this expression can be calculated by actionD. """ if raise_error: raise generic.ScriptError("This expression can not be assigned to a parameter", self.pos) return False def is_boolean(self): """ Check if this expression is limited to 0 or 1 as value. @return: True if the value of this expression is either 0 or 1. """ return False def type(self): """ Determine the datatype of this expression. @return: A constant from the L{Type} class, representing the data type. """ return Type.INTEGER class ConstantNumeric(Expression): def __init__(self, value, pos = None): Expression.__init__(self, pos) self.value = generic.truncate_int32(value) def debug_print(self, indentation): print indentation*' ' + 'Int:', self.value def write(self, file, size): file.print_varx(self.value, size) def __str__(self): return str(self.value) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): return True def is_boolean(self): return self.value == 0 or self.value == 1 def __eq__(self, other): return other is not None and isinstance(other, ConstantNumeric) and other.value == self.value def __hash__(self): return self.value class ConstantFloat(Expression): def __init__(self, value, pos): Expression.__init__(self, pos) self.value = float(value) def debug_print(self, indentation): print indentation*' ' + 'Float:', self.value def __str__(self): return str(self.value) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def type(self): return Type.FLOAT nml-0.2.4/nml/expression/bin_not.py0000644000061700006170000000676412036626441020037 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import Type, Expression, ConstantNumeric from .binop import BinOp from .boolean import Boolean class BinNot(Expression): def __init__(self, expr, pos = None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): print indentation*' ' + 'Binary not:' self.expr.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): expr = self.expr.reduce(id_dicts) if expr.type() != Type.INTEGER: raise generic.ScriptError("Not-operator (~) requires an integer argument.", expr.pos) if isinstance(expr, ConstantNumeric): return ConstantNumeric(0xFFFFFFFF ^ expr.value) if isinstance(expr, BinNot): return expr.expr return BinNot(expr) def supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def __str__(self): return "~" + str(self.expr) class Not(Expression): def __init__(self, expr, pos = None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): print indentation*' ' + 'Logical not:' self.expr.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): expr = self.expr.reduce(id_dicts) if expr.type() != Type.INTEGER: raise generic.ScriptError("Not-operator (!) requires an integer argument.", expr.pos) if isinstance(expr, ConstantNumeric): return ConstantNumeric(expr.value != 0) if isinstance(expr, Not): return Boolean(expr.expr).reduce() if isinstance(expr, BinOp): if expr.op == nmlop.CMP_EQ: return BinOp(nmlop.CMP_NEQ, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_NEQ: return BinOp(nmlop.CMP_EQ, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_LE: return BinOp(nmlop.CMP_GT, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_GE: return BinOp(nmlop.CMP_LT, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_LT: return BinOp(nmlop.CMP_GE, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_GT: return BinOp(nmlop.CMP_LE, expr.expr1, expr.expr2) if expr.op == nmlop.HASBIT: return BinOp(nmlop.NOTHASBIT, expr.expr1, expr.expr2) if expr.op == nmlop.NOTHASBIT: return BinOp(nmlop.HASBIT, expr.expr1, expr.expr2) return Not(expr) def supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def is_boolean(self): return True def __str__(self): return "!" + str(self.expr) nml-0.2.4/nml/expression/string.py0000644000061700006170000000402712036626441017703 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from .base_expression import Expression from .identifier import Identifier from nml import generic class String(Expression): def __init__(self, params, pos): Expression.__init__(self, pos) if len(params) == 0: raise generic.ScriptError("string() requires at least one parameter.", pos) self.name = params[0] if not isinstance(self.name, Identifier): raise generic.ScriptError("First parameter of string() must be an identifier.", pos) self.params = params[1:] def debug_print(self, indentation): print indentation*' ' + 'String:' self.name.debug_print(indentation + 2) for param in self.params: print (indentation+2)*' ' + 'Parameter:' param.debug_print(indentation + 4) def __str__(self): ret = 'string(' + self.name.value for p in self.params: ret += ', ' + str(p) ret += ')' return ret def reduce(self, id_dicts = [], unknown_id_fatal = True): params = [p.reduce(id_dicts) for p in self.params] return String([self.name] + params, self.pos) def __eq__(self, other): return other is not None and isinstance(other, String) and self.name == other.name and self.params == other.params def __hash__(self): return hash(self.name) ^ reduce(lambda x, y: x ^ hash(y), self.params, 0) nml-0.2.4/nml/expression/array.py0000644000061700006170000000236412036626441017515 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from .base_expression import Expression class Array(Expression): def __init__(self, values, pos): Expression.__init__(self, pos) self.values = values def debug_print(self, indentation): print indentation*' ' + 'Array of values:' for v in self.values: v.debug_print(indentation + 2) def __str__(self): return '[' + ', '.join([str(expr) for expr in self.values]) + ']' def reduce(self, id_dicts = [], unknown_id_fatal = True): return Array([val.reduce(id_dicts, unknown_id_fatal) for val in self.values], self.pos) nml-0.2.4/nml/expression/spritegroup_ref.py0000644000061700006170000000563712036626441021624 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import action2 from .base_expression import Type, Expression class SpriteGroupRef(Expression): """ Container for a reference to a sprite group / layout @ivar name: Name of the referenced item @type name: L{Identifier} @ivar param_list: List of parameters to be passed @type param_list: C{list} of L{Expression} @ivar pos: Position of this reference @type pos: L{Position} @ivar act2: Action2 that is the target of this reference To be used for action2s that have no direct equivalent in the AST @type act2: L{Action2} """ def __init__(self, name, param_list, pos, act2 = None): self.name = name self.param_list = param_list self.pos = pos self.act2 = act2 def debug_print(self, indentation): print indentation*' ' +'Reference to: ' + str(self.name) if len(self.param_list) != 0: print 'Parameters:' for p in self.param_list: p.debug_print(indentation + 2) def __str__(self): if self.param_list: return '%s(%s)' % (self.name, ', '.join(str(x) for x in self.param_list)) return str(self.name) def get_action2_id(self, feature): """ Get the action2 set-ID that this reference maps to @param feature: Feature of the action2 @type feature: C{int} @return: The set ID @rtype: C{int} """ if self.act2 is not None: return self.act2.id if self.name.value == 'CB_FAILED': return 0 # 0 serves as a failed CB result because it is never used try: spritegroup = action2.resolve_spritegroup(self.name) except generic.ScriptError: assert False, "Illegal action2 reference '%s' encountered." % self.name.value return spritegroup.get_action2(feature).id def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def type(self): return Type.SPRITEGROUP_REF def __eq__(self, other): return other is not None and isinstance(other, SpriteGroupRef) and other.name == self.name and other.param_list == self.param_list def __hash__(self): return hash(self.name) ^ hash(tuple(self.param_list)) nml-0.2.4/nml/expression/ternaryop.py0000644000061700006170000000554012036626441020421 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression, ConstantNumeric class TernaryOp(Expression): def __init__(self, guard, expr1, expr2, pos): Expression.__init__(self, pos) self.guard = guard self.expr1 = expr1 self.expr2 = expr2 def debug_print(self, indentation): print indentation*' ' + 'Ternary operator' print indentation*' ' + 'Guard:' self.guard.debug_print(indentation + 2) print indentation*' ' + 'Expression 1:' self.expr1.debug_print(indentation + 2) print indentation*' ' + 'Expression 2:' self.expr2.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): guard = self.guard.reduce(id_dicts) expr1 = self.expr1.reduce(id_dicts) expr2 = self.expr2.reduce(id_dicts) if isinstance(guard, ConstantNumeric): if guard.value != 0: return expr1 else: return expr2 if guard.type() != Type.INTEGER or expr1.type() != Type.INTEGER or expr2.type() != Type.INTEGER: raise generic.ScriptError("All parts of the ternary operator (?:) must be integers.", self.pos) return TernaryOp(guard, expr1, expr2, self.pos) def supported_by_action2(self, raise_error): return self.guard.supported_by_action2(raise_error) and self.expr1.supported_by_action2(raise_error) and self.expr2.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.guard.supported_by_actionD(raise_error) and self.expr1.supported_by_actionD(raise_error) and self.expr2.supported_by_actionD(raise_error) def is_boolean(self): return self.expr1.is_boolean() and self.expr2.is_boolean() def __eq__(self, other): return other is not None and isinstance(other, TernaryOp) and self.guard == other.guard and self.expr1 == other.expr1 and self.expr2 == other.expr2 def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.guard, self.expr1, self.expr2)) def __str__(self): return "(%s ? %s : %s)" % (str(self.guard), str(self.expr1), str(self.expr2)) nml-0.2.4/nml/expression/boolean.py0000644000061700006170000000321612036626441020013 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression class Boolean(Expression): def __init__(self, expr, pos = None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): print indentation*' ' + 'Force expression to boolean:' self.expr.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): expr = self.expr.reduce(id_dicts) if expr.type() != Type.INTEGER: raise generic.ScriptError("Only integers can be converted to a boolean value.", self.pos) if expr.is_boolean(): return expr return Boolean(expr) def supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def is_boolean(self): return True def __str__(self): return "!!(%s)" % str(self.expr) nml-0.2.4/nml/expression/variable.py0000644000061700006170000000752212036626441020165 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression, ConstantNumeric class Variable(Expression): def __init__(self, num, shift = None, mask = None, param = None, pos = None): Expression.__init__(self, pos) self.num = num self.shift = shift if shift is not None else ConstantNumeric(0) self.mask = mask if mask is not None else ConstantNumeric(0xFFFFFFFF) self.param = param self.add = None self.div = None self.mod = None self.extra_params = [] def debug_print(self, indentation): print indentation*' ' + 'Action2 variable' self.num.debug_print(indentation + 2) if self.param is not None: print (indentation+2)*' ' + 'Parameter:' if isinstance(self.param, basestring): print (indentation+4)*' ' + 'Procedure call:', self.param else: self.param.debug_print(indentation + 4) if len(self.extra_params) > 0: print (indentation+2)*' ' + 'Extra parameters:' for extra_param in self.extra_params: extra_param.debug_print(indentation + 4) def __str__(self): num = "0x%02X" % self.num.value if isinstance(self.num, ConstantNumeric) else str(self.num) ret = 'var[%s, %s, %s' % (num, str(self.shift), str(self.mask)) if self.param is not None: ret += ', %s' % str(self.param) ret += ']' if self.add is not None: ret = '(%s + %s)' % (ret, self.add) if self.div is not None: ret = '(%s / %s)' % (ret, self.div) if self.mod is not None: ret = '(%s %% %s)' % (ret, self.mod) return ret def reduce(self, id_dicts = [], unknown_id_fatal = True): num = self.num.reduce(id_dicts) shift = self.shift.reduce(id_dicts) mask = self.mask.reduce(id_dicts) param = self.param.reduce(id_dicts) if self.param is not None else None if not all(map(lambda x: x.type() == Type.INTEGER, (num, shift, mask))) or \ (param is not None and param.type() != Type.INTEGER): raise generic.ScriptError("All parts of a variable access must be integers.", self.pos) var = Variable(num, shift, mask, param, self.pos) var.add = None if self.add is None else self.add.reduce(id_dicts) var.div = None if self.div is None else self.div.reduce(id_dicts) var.mod = None if self.mod is None else self.mod.reduce(id_dicts) var.extra_params = [(extra_param[0], extra_param[1].reduce(id_dicts)) for extra_param in self.extra_params] return var def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): if raise_error: if isinstance(self.num, ConstantNumeric): if self.num.value == 0x7C: raise generic.ScriptError("LOAD_PERM is only available in switch-blocks.", self.pos) if self.num.value == 0x7D: raise generic.ScriptError("LOAD_TEMP is only available in switch-blocks.", self.pos) raise generic.ScriptError("Variable accesses are not supported outside of switch-blocks.", self.pos) return False nml-0.2.4/nml/expression/__init__.py0000644000061700006170000000304612036626441020134 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import re from .array import Array from .base_expression import Type, Expression, ConstantNumeric, ConstantFloat from .bin_not import BinNot, Not from .binop import BinOp from .bitmask import BitMask from .boolean import Boolean from .functioncall import FunctionCall, SpecialCheck, GRMOp from .functionptr import FunctionPtr from .identifier import Identifier from .patch_variable import PatchVariable from .parameter import Parameter, OtherGRFParameter, parse_string_to_dword from .special_parameter import SpecialParameter from .spritegroup_ref import SpriteGroupRef from .storage_op import StorageOp from .string import String from .string_literal import StringLiteral from .ternaryop import TernaryOp from .variable import Variable is_valid_id = re.compile('[a-zA-Z_][a-zA-Z0-9_]{3}$') def identifier_to_print(value): if is_valid_id.match(value): return value return '"%s"' % value nml-0.2.4/nml/expression/functionptr.py0000644000061700006170000000426512036626441020754 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression class FunctionPtr(Expression): """ Pointer to a function. If this appears inside an expression, the user has made an error. @ivar name Identifier that has been resolved to this function pointer. @type name L{Identifier} @ivar func Function that will be called to resolve this function call. Arguments: Name of the function (C{basestring}) List of passed arguments (C{list} of L{Expression}) Position information (L{Position}) Any extra arguments passed to the constructor of this class @type func C{function} @ivar extra_args List of arguments that should be passed to the function that is to be called. @type extra_args C{list} """ def __init__(self, name, func, *extra_args): self.name = name self.func = func self.extra_args = extra_args def debug_print(self, indentation): assert False, "Function pointers should not appear inside expressions." def __str__(self): assert False, "Function pointers should not appear inside expressions." def reduce(self, id_dicts = [], unknown_id_fatal = True): raise generic.ScriptError("'%s' is a function and should be called using the function call syntax." % str(self.name), self.name.pos) def type(self): return Type.FUNCTION_PTR def call(self, args): return self.func(self.name.value, args, self.name.pos, *self.extra_args) nml-0.2.4/nml/expression/patch_variable.py0000644000061700006170000000303212036626441021334 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression class PatchVariable(Expression): """ Class for reading so-called 'patch variables' via a special ActionD @ivar num: Variable number to read @type num: C{int} """ def __init__(self, num, pos = None): Expression.__init__(self, pos) self.num = num def debug_print(self, indentation): print indentation*' ' + 'PatchVariable: ' + str(self.num) def __str__(self): return "PatchVariable(%d)" % self.num def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): if raise_error: raise generic.ScriptError("Reading patch variables is not supported in a switch-block.", self.pos) return False def supported_by_actionD(self, raise_error): return True nml-0.2.4/nml/expression/string_literal.py0000644000061700006170000000244412036626441021420 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from .base_expression import Type, Expression class StringLiteral(Expression): def __init__(self, value, pos): Expression.__init__(self, pos) self.value = value def debug_print(self, indentation): print indentation*' ' + 'String literal: "%s"' % self.value def __str__(self): return '"%s"' % self.value def write(self, file, size): assert(len(self.value) == size) file.print_string(self.value, final_zero = False, force_ascii = True) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def type(self): return Type.STRING_LITERAL nml-0.2.4/nml/expression/bitmask.py0000644000061700006170000000351012036626441020023 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import Type, Expression, ConstantNumeric from .binop import BinOp class BitMask(Expression): def __init__(self, values, pos): Expression.__init__(self, pos) self.values = values def debug_print(self, indentation): print indentation*' ' + 'Get bitmask:' for value in self.values: value.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): ret = ConstantNumeric(0, self.pos) for orig_expr in self.values: val = orig_expr.reduce(id_dicts) if val.type() != Type.INTEGER: raise generic.ScriptError("Parameters of 'bitmask' must be integers.", orig_expr.pos) if isinstance(val, ConstantNumeric) and val.value >= 32: raise generic.ScriptError("Parameters of 'bitmask' cannot be greater than 31", orig_expr.pos) val = BinOp(nmlop.SHIFT_LEFT, ConstantNumeric(1), val, val.pos) ret = BinOp(nmlop.OR, ret, val, self.pos) return ret.reduce() def __str__(self): return "bitmask(" + ", ".join(str(e) for e in self.values) + ")" nml-0.2.4/nml/expression/binop.py0000644000061700006170000002366112036626441017511 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import Expression, ConstantNumeric, ConstantFloat from .string_literal import StringLiteral from .variable import Variable from .boolean import Boolean class BinOp(Expression): def __init__(self, op, expr1, expr2, pos = None): Expression.__init__(self, pos) self.op = op self.expr1 = expr1 self.expr2 = expr2 def debug_print(self, indentation): print indentation*' ' + 'Binary operator, op = ', self.op.token self.expr1.debug_print(indentation + 2) self.expr2.debug_print(indentation + 2) def __str__(self): if self.op == nmlop.SUB and isinstance(self.expr1, ConstantNumeric) and self.expr1.value == 0: return '-' + str(self.expr2) return self.op.to_string(self.expr1, self.expr2) def get_priority(self, expr): """ Get the priority of an expression. For optimalization reason we prefer complexer expressions (= low priority) on the left hand side of this expression. The following priorities are used: -1: everything that doesn't fit in one of the other categories. 0: Variables to be parsed in a varaction2 1: Expressions that can be parsed via actionD, with the exception of constant numbers 2: constant numbers @param expr: The expression to get the priority of. @type expr: L{Expression} @return: The priority for the given expression. @rtype: C{int} """ if isinstance(expr, Variable): return 0 if isinstance(expr, ConstantNumeric): return 2 if expr.supported_by_actionD(False): return 1 return -1 def reduce(self, id_dicts = [], unknown_id_fatal = True): # Reducing a BinOp expression is done in several phases: # - Reduce both subexpressions. # - If both subexpressions are constant, compute the result and return it. # - If the operator allows it and the second expression is more complex than # the first one swap them. # - If the operation is a no-op, delete it. # - Variables (as used in action2var) can have some computations attached to # them, do that if possible. # - Try to merge multiple additions/subtractions with constant numbers # - Reduce both subexpressions. expr1 = self.expr1.reduce(id_dicts) expr2 = self.expr2.reduce(id_dicts) # Make sure the combination of operands / operator is valid if self.op.validate_func is not None: self.op.validate_func(expr1, expr2, self.pos) # - If both subexpressions are constant, compute the result and return it. if isinstance(expr1, ConstantNumeric) and isinstance(expr2, ConstantNumeric) and self.op.compiletime_func is not None: return ConstantNumeric(self.op.compiletime_func(expr1.value, expr2.value), self.pos) if isinstance(expr1, StringLiteral) and isinstance(expr2, StringLiteral): assert self.op == nmlop.ADD return StringLiteral(expr1.value + expr2.value, expr1.pos) if isinstance(expr1, (ConstantNumeric, ConstantFloat)) and isinstance(expr2, (ConstantNumeric, ConstantFloat)) and self.op.compiletime_func is not None: return ConstantFloat(self.op.compiletime_func(expr1.value, expr2.value), self.pos) # - If the operator allows it and the second expression is more complex than # the first one swap them. op = self.op if op in commutative_operators or self.op in (nmlop.CMP_LT, nmlop.CMP_GT): prio1 = self.get_priority(expr1) prio2 = self.get_priority(expr2) if prio2 < prio1: expr1, expr2 = expr2, expr1 if op == nmlop.CMP_LT: op = nmlop.CMP_GT elif op == nmlop.CMP_GT: op = nmlop.CMP_LT # - If the operation is a no-op, delete it. if op == nmlop.AND and isinstance(expr2, ConstantNumeric) and (expr2.value == -1 or expr2.value == 0xFFFFFFFF): return expr1 if op in (nmlop.DIV, nmlop.DIVU, nmlop.MUL) and isinstance(expr2, ConstantNumeric) and expr2.value == 1: return expr1 if op in (nmlop.ADD, nmlop.SUB) and isinstance(expr2, ConstantNumeric) and expr2.value == 0: return expr1 # - Variables (as used in action2var) can have some computations attached to # them, do that if possible. if isinstance(expr1, Variable) and expr2.supported_by_actionD(False): # An action2 Variable has some special fields (mask, add, div and mod) that can be used # to perform some operations on the value. These operations are faster than a normal # advanced varaction2 operator so we try to use them whenever we can. if op == nmlop.AND and expr1.add is None: expr1.mask = BinOp(nmlop.AND, expr1.mask, expr2, self.pos).reduce(id_dicts) return expr1 if op == nmlop.ADD and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = expr2 else: expr1.add = BinOp(nmlop.ADD, expr1.add, expr2, self.pos).reduce(id_dicts) return expr1 if op == nmlop.SUB and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = ConstantNumeric(0) expr1.add = BinOp(nmlop.SUB, expr1.add, expr2, self.pos).reduce(id_dicts) return expr1 # The div and mod fields cannot be used at the same time. Also whenever either of those # two are used the add field has to be set, so we change it to zero when it's not yet set. if op == nmlop.DIV and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = ConstantNumeric(0) expr1.div = expr2 return expr1 if op == nmlop.MOD and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = ConstantNumeric(0) expr1.mod = expr2 return expr1 # Since we have a lot of nml-variables that are in fact only the high bits of an nfo # variable it can happen that we want to shift back the variable to the left. # Don't use any extra opcodes but just reduce the shift-right in that case. if op == nmlop.SHIFT_LEFT and isinstance(expr2, ConstantNumeric) and expr1.add is None and expr2.value < expr1.shift.value: expr1.shift.value -= expr2.value expr1.mask = BinOp(nmlop.SHIFT_LEFT, expr1.mask, expr2).reduce() return expr1 # - Try to merge multiple additions/subtractions with constant numbers if op in (nmlop.ADD, nmlop.SUB) and isinstance(expr2, ConstantNumeric) and \ isinstance(expr1, BinOp) and expr1.op in (nmlop.ADD, nmlop.SUB) and isinstance(expr1.expr2, ConstantNumeric): val = expr2.value if op == nmlop.ADD else -expr2.value if expr1.op == nmlop.ADD: return BinOp(nmlop.ADD, expr1.expr1, ConstantNumeric(expr1.expr2.value + val), self.pos).reduce() if expr1.op == nmlop.SUB: return BinOp(nmlop.SUB, expr1.expr1, ConstantNumeric(expr1.expr2.value - val), self.pos).reduce() if op == nmlop.OR and isinstance(expr1, Boolean) and isinstance(expr2, Boolean): return Boolean(BinOp(op, expr1.expr, expr2.expr, self.pos)).reduce(id_dicts) return BinOp(op, expr1, expr2, self.pos) def supported_by_action2(self, raise_error): if not self.op.act2_supports: token = " '%s'" % self.op.token if self.op.token else "" if raise_error: raise generic.ScriptError("Operator%s not supported in a switch-block" % token, self.pos) return False return self.expr1.supported_by_action2(raise_error) and self.expr2.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): if not self.op.actd_supports: token = " '%s'" % self.op.token if self.op.token else "" if raise_error: if self.op == nmlop.STO_PERM: raise generic.ScriptError("STORE_PERM is only available in switch-blocks.", self.pos) elif self.op == nmlop.STO_TMP: raise generic.ScriptError("STORE_TEMP is only available in switch-blocks.", self.pos) #default case raise generic.ScriptError("Operator%s not supported in parameter assignment" % token, self.pos) return False return self.expr1.supported_by_actionD(raise_error) and self.expr2.supported_by_actionD(raise_error) def is_boolean(self): if self.op in (nmlop.AND, nmlop.OR, nmlop.XOR): return self.expr1.is_boolean() and self.expr2.is_boolean() return self.op.returns_boolean def __eq__(self, other): return other is not None and isinstance(other, BinOp) and self.op == other.op and self.expr1 == other.expr1 and self.expr2 == other.expr2 def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.op, self.expr1, self.expr2)) commutative_operators = set([ nmlop.ADD, nmlop.MUL, nmlop.AND, nmlop.OR, nmlop.XOR, nmlop.CMP_EQ, nmlop.CMP_NEQ, nmlop.MIN, nmlop.MAX, ]) nml-0.2.4/nml/expression/storage_op.py0000644000061700006170000001101312036626441020530 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import ConstantNumeric, Expression, Type from .parameter import parse_string_to_dword from .string_literal import StringLiteral storage_op_info = { 'STORE_PERM' : {'store': True, 'perm': True, 'grfid': False, 'max': 0x0F}, 'STORE_TEMP' : {'store': True, 'perm': False, 'grfid': False, 'max': 0x10F}, 'LOAD_PERM' : {'store': False, 'perm': True, 'grfid': True, 'max': 0x0F}, 'LOAD_TEMP' : {'store': False, 'perm': False, 'grfid': False, 'max': 0xFF}, } class StorageOp(Expression): """ Class for reading/writing to (temporary or permanent) storage @ivar name: Name of the called storage function @type name: C{str} or C{unicode} @ivar info: Dictionary containting information about the operation to perform @type info: C{dict} @ivar value: Value to store, or C{None} for loading operations @type value: L{Expression} @ivar register: Register to access @type register: L{Expression} @ivar grfid: GRFID of the register to access @type grfid: L{Expression} """ def __init__(self, name, args, pos = None): Expression.__init__(self, pos) self.name = name assert name in storage_op_info self.info = storage_op_info[name] arg_len = (2,) if self.info['store'] else (1,) if self.info['grfid']: arg_len += (arg_len[0] + 1,) if len(args) not in arg_len: argstr = "%d" % arg_len[0] if len(arg_len) == 1 else "%d..%d" % arg_len raise generic.ScriptError("%s requires %s argument(s), encountered %d" % (name, argstr, len(args)), pos) i = 0 if self.info['store']: self.value = args[i] i += 1 else: self.value = None self.register = args[i] i += 1 if i < len(args): self.grfid = args[i] assert self.info['grfid'] else: self.grfid = None def debug_print(self, indentation): print indentation*' ' + self.name print (indentation+2)*' ' + 'Register:' self.register.debug_print(indentation + 4) if self.value is not None: print (indentation+2)*' ' + 'Value:' self.value.debug_print(indentation + 4) if self.grfid is not None: print (indentation+2)*' ' + 'GRFID:' self.grfid.debug_print(indentation + 4) def __str__(self): args = [] if self.value is not None: args.append(str(self.value)) args.append(str(self.register)) if self.grfid is not None: args.append(str(self.grfid)) return "%s(%s)" % (self.name, ", ".join(args)) def reduce(self, id_dicts = [], unknown_id_fatal = True): args = [] if self.value is not None: value = self.value.reduce(id_dicts) if value.type() != Type.INTEGER: raise generic.ScriptError("Value to store must be an integer.", value.pos) args.append(value) register = self.register.reduce(id_dicts) if register.type() != Type.INTEGER: raise generic.ScriptError("Register to access must be an integer.", register.pos) if isinstance(register, ConstantNumeric) and register.value > self.info['max']: raise generic.ScriptError("Maximum register for %s is %d" % (self.name, self.info['max']), self.pos) args.append(register) if self.grfid is not None: grfid = self.grfid.reduce(id_dicts) # Test validity parse_string_to_dword(grfid) args.append(grfid) return StorageOp(self.name, args, self.pos) def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): if raise_error: raise generic.ScriptError("%s() may only be used inside switch-blocks" % self.name, self.pos) return False nml-0.2.4/nml/expression/functioncall.py0000644000061700006170000005627412036626442021072 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import datetime, calendar, math from nml.actions import action11 from nml import generic, nmlop from .base_expression import Type, Expression, ConstantNumeric, ConstantFloat from .binop import BinOp from .bitmask import BitMask from .parameter import parse_string_to_dword from .storage_op import StorageOp from .string_literal import StringLiteral from .ternaryop import TernaryOp import identifier class FunctionCall(Expression): def __init__(self, name, params, pos): Expression.__init__(self, pos) self.name = name self.params = params def debug_print(self, indentation): print indentation*' ' + 'Call function: ' + self.name.value for param in self.params: print (indentation+2)*' ' + 'Parameter:' param.debug_print(indentation + 4) def __str__(self): ret = '' for param in self.params: if ret == '': ret = str(param) else: ret = '%s, %s' % (ret, str(param)) ret = '%s(%s)' % (self.name, ret) return ret def reduce(self, id_dicts = [], unknown_id_fatal = True): if self.name.value in function_table: func = function_table[self.name.value] val = func(self.name.value, self.params, self.pos) return val.reduce(id_dicts) else: #try user-defined functions func_ptr = self.name.reduce(id_dicts, unknown_id_fatal = False, search_func_ptr = True) if func_ptr != self.name: # we found something! if func_ptr.type() == Type.SPRITEGROUP_REF: func_ptr.param_list = self.params return func_ptr if func_ptr.type() != Type.FUNCTION_PTR: raise generic.ScriptError("'%s' is defined, but it is not a function." % self.name.value, self.pos) return func_ptr.call(self.params) if unknown_id_fatal: raise generic.ScriptError("'%s' is not defined as a function." % self.name.value, self.pos) return FunctionCall(self.name, self.params, self.pos) class SpecialCheck(Expression): """ Action7/9 special check (e.g. to see whether a cargo is defined) @ivar op: Action7/9 operator to use @type op: (C{int}, C{basestring})-tuple @ivar varnum: Variable number to read @type varnum: C{int} @ivar results: Result of the check when skipping (0) or not skipping (1) @type results: (C{int}, C{int})-tuple @ivar value: Value to test @type value: C{int} @ivar mask: Mask to to test only certain bits of the value @type mask: C{int} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, op, varnum, results, value, to_string, mask = None, pos = None): Expression.__init__(self, pos) self.op = op self.varnum = varnum self.results = results self.value = value self.to_string = to_string self.mask = mask def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def __str__(self): return self.to_string def supported_by_actionD(self, raise_error): return True class GRMOp(Expression): def __init__(self, op, feature, count, to_string, pos = None): Expression.__init__(self, pos) self.op = op self.feature = feature self.count = count self.to_string = to_string def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def __str__(self): return self.to_string(self) def supported_by_actionD(self, raise_error): return True #{ Builtin functions def builtin_min(name, args, pos): """ min(...) builtin function. @return Lowest value of the given arguments. """ if len(args) < 2: raise generic.ScriptError("min() requires at least 2 arguments", pos) return reduce(lambda x, y: BinOp(nmlop.MIN, x, y, pos), args) def builtin_max(name, args, pos): """ max(...) builtin function. @return Heighest value of the given arguments. """ if len(args) < 2: raise generic.ScriptError("max() requires at least 2 arguments", pos) return reduce(lambda x, y: BinOp(nmlop.MAX, x, y, pos), args) def builtin_date(name, args, pos): """ date(year, month, day) builtin function. @return Days since 1 jan 1 of the given date. """ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if len(args) != 3: raise generic.ScriptError("date() requires exactly 3 arguments", pos) from nml import global_constants identifier.ignore_all_invalid_ids = True year = args[0].reduce(global_constants.const_list) identifier.ignore_all_invalid_ids = False try: month = args[1].reduce_constant().value day = args[2].reduce_constant().value except generic.ConstError: raise generic.ScriptError("Month and day parameters of date() should be compile-time constants", pos) generic.check_range(month, 1, 12, "month", args[1].pos) generic.check_range(day, 1, days_in_month[month-1], "day", args[2].pos) if not isinstance(year, ConstantNumeric): if month != 1 or day != 1: raise generic.ScriptError("when the year parameter of date() is not a compile time constant month and day should be 1", pos) #num_days = year*365 + year/4 - year/100 + year/400 part1 = BinOp(nmlop.MUL, year, ConstantNumeric(365)) part2 = BinOp(nmlop.DIV, year, ConstantNumeric(4)) part3 = BinOp(nmlop.DIV, year, ConstantNumeric(100)) part4 = BinOp(nmlop.DIV, year, ConstantNumeric(400)) res = BinOp(nmlop.ADD, part1, part2) res = BinOp(nmlop.SUB, res, part3) res = BinOp(nmlop.ADD, res, part4) return res generic.check_range(year.value, 0, 5000000, "year", year.pos) day_in_year = 0 for i in range(month - 1): day_in_year += days_in_month[i] day_in_year += day if month >= 3 and (year.value % 4 == 0) and ((not year.value % 100 == 0) or (year.value % 400 == 0)): day_in_year += 1 return ConstantNumeric(year.value * 365 + calendar.leapdays(0, year.value) + day_in_year - 1, pos) def builtin_day_of_year(name, args, pos): """ day_of_year(month, day) builtin function. @return Day of the year, assuming February has 28 days. """ if len(args) != 2: raise generic.ScriptError(name + "() must have a month and a day parameter", pos) month = args[0].reduce() if not isinstance(month, ConstantNumeric): raise generic.ScriptError('Month should be a compile-time constant.', month.pos) if month.value < 1 or month.value > 12: raise generic.ScriptError('Month should be a value between 1 and 12.', month.pos) day = args[1].reduce() if not isinstance(day, ConstantNumeric): raise generic.ScriptError('Day should be a compile-time constant.', day.pos) # Mapping of month to number of days in that month. number_days = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31} if day.value < 1 or day.value > number_days[month.value]: raise generic.ScriptError('Day should be value between 1 and %d.' % number_days[month.value], day.pos) return ConstantNumeric(datetime.date(1, month.value, day.value).toordinal(), pos) def builtin_storage(name, args, pos): """ Accesses to temporary / persistent storage """ return StorageOp(name, args, pos) def builtin_ucmp(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.VACT2_UCMP, args[0], args[1], pos) def builtin_cmp(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.VACT2_CMP, args[0], args[1], pos) def builtin_rotate(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.ROT_RIGHT, args[0], args[1], pos) def builtin_hasbit(name, args, pos): """ hasbit(value, bit_num) builtin function. @return C{1} if and only if C{value} has bit C{bit_num} set, C{0} otherwise. """ if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.HASBIT, args[0], args[1], pos) def builtin_version_openttd(name, args, pos): """ version_openttd(major, minor, revision[, build]) builtin function. @return The version information encoded in a double-word. """ if len(args) > 4 or len(args) < 3: raise generic.ScriptError(name + "() must have 3 or 4 parameters", pos) major = args[0].reduce_constant().value minor = args[1].reduce_constant().value revision = args[2].reduce_constant().value build = args[3].reduce_constant().value if len(args) == 4 else 0x80000 return ConstantNumeric((major << 28) | (minor << 24) | (revision << 20) | build) def builtin_cargotype_available(name, args, pos): """ cargotype_available(cargo_label) builtin function. @return 1 if the cargo label is available, 0 otherwise. """ if len(args) != 1: raise generic.ScriptError(name + "() must have exactly 1 parameter", pos) label = args[0].reduce() return SpecialCheck((0x0B, r'\7c'), 0, (0, 1), parse_string_to_dword(label), "%s(%s)" % (name, str(label)), pos = args[0].pos) def builtin_railtype_available(name, args, pos): """ railtype_available(cargo_label) builtin function. @return 1 if the railtype label is available, 0 otherwise. """ if len(args) != 1: raise generic.ScriptError(name + "() must have exactly 1 parameter", pos) label = args[0].reduce() return SpecialCheck((0x0D, None), 0, (0, 1), parse_string_to_dword(label), "%s(%s)" % (name, str(label)), pos = args[0].pos) def builtin_grf_status(name, args, pos): """ grf_(current_status|future_status|order_behind)(grfid[, mask]) builtin function. @return 1 if the grf is, or will be, active, 0 otherwise. """ if len(args) not in (1, 2): raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) labels = [label.reduce() for label in args] mask = parse_string_to_dword(labels[1]) if len(labels) > 1 else None if name == 'grf_current_status': op = (0x06, r'\7G') results = (1, 0) elif name == 'grf_future_status': op = (0x0A, r'\7gg') results = (0, 1) elif name == 'grf_order_behind': op = (0x08, r'\7gG') results = (0, 1) else: assert False, "Unknown grf status function" if mask is None: string = "%s(%s)" % (name, str(label)) else: string = "%s(%s, %s)" % (name, str(label), str(mask)) return SpecialCheck(op, 0x88, results, parse_string_to_dword(labels[0]), string, mask, args[0].pos) def builtin_visual_effect_and_powered(name, args, pos): """ Builtin function, used in two forms: visual_effect_and_powered(effect, offset, powered) visual_effect(effect, offset) Use this to set the vehicle property visual_effect[_and_powered] and for the callback VEH_CB_VISUAL_EFFECT[_AND_POWERED] """ arg_len = 2 if name == 'visual_effect' else 3 if len(args) != arg_len: raise generic.ScriptError(name + "() must have %d parameters" % arg_len, pos) from nml import global_constants effect = args[0].reduce_constant(global_constants.const_list).value offset = BinOp(nmlop.ADD, args[1], ConstantNumeric(8), args[1].pos).reduce_constant().value generic.check_range(offset, 0, 0x0F, "offset in function " + name, pos) if arg_len == 3: powered = args[2].reduce_constant(global_constants.const_list).value if powered != 0 and powered != 0x80: raise generic.ScriptError("3rd argument to visual_effect_and_powered (powered) must be either ENABLE_WAGON_POWER or DISABLE_WAGON_POWER", pos) else: powered = 0 return ConstantNumeric(effect | offset | powered) def builtin_str2number(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) return ConstantNumeric(parse_string_to_dword(args[0])) def builtin_cargotype(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) from nml import global_constants if not isinstance(args[0], StringLiteral) or args[0].value not in global_constants.cargo_numbers: raise generic.ScriptError("Parameter for " + name + "() must be a string literal that is also in your cargo table", pos) return ConstantNumeric(global_constants.cargo_numbers[args[0].value]) def builtin_railtype(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) from nml import global_constants if not isinstance(args[0], StringLiteral) or args[0].value not in global_constants.railtype_table: raise generic.ScriptError("Parameter for " + name + "() must be a string literal that is also in your railtype table", pos) return ConstantNumeric(global_constants.railtype_table[args[0].value]) def builtin_reserve_sprites(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) count = args[0].reduce_constant() func = lambda x: '%s(%d)' % (name, count.value) return GRMOp(nmlop.GRM_RESERVE, 0x08, count.value, func, pos) def builtin_industry_type(name, args, pos): """ industry_type(IND_TYPE_OLD | IND_TYPE_NEW, id) builtin function @return The industry type in the format used by grfs (industry prop 0x16 and var 0x64) """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) from nml import global_constants type = args[0].reduce_constant(global_constants.const_list).value if type not in (0, 1): raise generic.ScriptError("First argument of industry_type() must be IND_TYPE_OLD or IND_TYPE_NEW", pos) # Industry ID uses 6 bits (0 .. 5), so bit 6 is never used id = args[1].reduce_constant(global_constants.const_list).value if not 0 <= id <= 63: raise generic.ScriptError("Second argument 'id' of industry_type() must be in range 0..63", pos) return ConstantNumeric(type << 7 | id) def builtin_trigonometric(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) trigonometric_func_table = { 'acos': math.acos, 'asin': math.asin, 'atan': math.atan, 'cos': math.cos, 'sin': math.sin, 'tan': math.tan, } return ConstantFloat(trigonometric_func_table[name](val.value), val.pos) def builtin_int(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) return ConstantNumeric(int(val.value), val.pos) def builtin_abs(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) guard = BinOp(nmlop.CMP_LT, args[0], ConstantNumeric(0), args[0].pos) return TernaryOp(guard, BinOp(nmlop.SUB, ConstantNumeric(0), args[0], args[0].pos), args[0], args[0].pos).reduce() def builtin_sound_file(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if not isinstance(args[0], StringLiteral): raise generic.ScriptError("Parameter for " + name + "() must be a string literal", pos) return ConstantNumeric(action11.add_sound(args[0].value), pos) def builtin_sound_import(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) grfid = parse_string_to_dword(args[0].reduce()) sound_num = args[1].reduce_constant().value return ConstantNumeric(action11.add_sound((grfid, sound_num)), pos) def builtin_relative_coord(name, args, pos): """ relative_coord(x, y) builtin function. @return Coordinates in 0xYYXX format. """ if len(args) != 2: raise generic.ScriptError(name + "() must have x and y coordinates as parameters", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 255, "Argument of '%s'" % name, args[0].pos) if isinstance(args[1], ConstantNumeric): generic.check_range(args[1].value, 0, 255, "Argument of '%s'" % name, args[1].pos) x_coord = BinOp(nmlop.AND, args[0], ConstantNumeric(0xFF), args[0].pos) y_coord = BinOp(nmlop.AND, args[1], ConstantNumeric(0xFF), args[1].pos) # Shift Y to its position. y_coord = BinOp(nmlop.SHIFT_LEFT, y_coord, ConstantNumeric(8), y_coord.pos) return BinOp(nmlop.OR, x_coord, y_coord, pos) def builtin_num_corners_raised(name, args, pos): """ num_corners_raised(slope) builtin function. slope is a 5-bit value @return Number of raised corners in a slope (4 for steep slopes) """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) slope = args[0] # The returned value is ((slope x 0x8421) & 0x11111) % 0xF # Explanation in steps: (numbers in binary) # - Masking constrains the slope to 5 bits, just to be sure (a|bcde) # - Multiplication creates 4 copies of those bits (abcd|eabc|deab|cdea|bcde) # - And-masking leaves only the lowest bit in each nibble (000d|000c|000b|000a|000e) # - The modulus operation adds one to the output for each set bit # - We now have the count of bits in the slope, which is wat we want. yay! slope = BinOp(nmlop.AND, slope, ConstantNumeric(0x1F), pos) slope = BinOp(nmlop.MUL, slope, ConstantNumeric(0x8421), pos) slope = BinOp(nmlop.AND, slope, ConstantNumeric(0x11111), pos) return BinOp(nmlop.MOD, slope, ConstantNumeric(0xF), pos) def builtin_slope_to_sprite_offset(name, args, pos): """ builtin function slope_to_sprite_offset(slope) @return sprite offset to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '%s'" % name, args[0].pos) # step 1: ((slope >= 0) & (slope <= 14)) * slope # This handles all non-steep slopes expr = BinOp(nmlop.AND, BinOp(nmlop.CMP_LE, args[0], ConstantNumeric(14), pos), BinOp(nmlop.CMP_GE, args[0], ConstantNumeric(0), pos), pos) expr = BinOp(nmlop.MUL, expr, args[0], pos) # Now handle the steep slopes separately # So add (slope == SLOPE_XX) * offset_of_SLOPE_XX for each steep slope steep_slopes = [(23, 16), (27, 17), (29, 15), (30, 18)] for slope, offset in steep_slopes: to_add = BinOp(nmlop.MUL, BinOp(nmlop.CMP_EQ, args[0], ConstantNumeric(slope), pos), ConstantNumeric(offset), pos) expr = BinOp(nmlop.ADD, expr, to_add, pos) return expr def builtin_palette_1cc(name, args, pos): """ palette_1cc(colour) builtin function. @return Recolour sprite to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '%s'" % name, args[0].pos) return BinOp(nmlop.ADD, args[0], ConstantNumeric(775), pos) def builtin_palette_2cc(name, args, pos): """ palette_2cc(colour1, colour2) builtin function. @return Recolour sprite to use """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for i in range(0, 2): if isinstance(args[i], ConstantNumeric): generic.check_range(args[i].value, 0, 15, "Argument of '%s'" % name, args[i].pos) col2 = BinOp(nmlop.MUL, args[1], ConstantNumeric(16), pos) col12 = BinOp(nmlop.ADD, col2, args[0], pos) # Base sprite is not a constant from nml import global_constants base = global_constants.patch_variable(global_constants.patch_variables['base_sprite_2cc'], pos) return BinOp(nmlop.ADD, col12, base, pos) def builtin_vehicle_curv_info(name, args, pos): """ vehicle_curv_info(prev_cur, cur_next) builtin function @return Value to use with vehicle var curv_info """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for arg in args: if isinstance(arg, ConstantNumeric): generic.check_range(arg.value, -2, 2, "Argument of '%s'" % name, arg.pos) args = [BinOp(nmlop.AND, arg, ConstantNumeric(0xF), pos) for arg in args] cur_next = BinOp(nmlop.SHIFT_LEFT, args[1], ConstantNumeric(8), pos) return BinOp(nmlop.OR, args[0], cur_next, pos) #} function_table = { 'min' : builtin_min, 'max' : builtin_max, 'date' : builtin_date, 'day_of_year' : builtin_day_of_year, 'bitmask' : lambda name, args, pos: BitMask(args, pos), 'STORE_TEMP' : builtin_storage, 'STORE_PERM' : builtin_storage, 'LOAD_TEMP' : builtin_storage, 'LOAD_PERM' : builtin_storage, 'hasbit' : builtin_hasbit, 'version_openttd' : builtin_version_openttd, 'cargotype_available' : builtin_cargotype_available, 'railtype_available' : builtin_railtype_available, 'grf_current_status' : builtin_grf_status, 'grf_future_status' : builtin_grf_status, 'grf_order_behind' : builtin_grf_status, 'visual_effect' : builtin_visual_effect_and_powered, 'visual_effect_and_powered' : builtin_visual_effect_and_powered, 'str2number' : builtin_str2number, 'cargotype' : builtin_cargotype, 'railtype' : builtin_railtype, 'reserve_sprites' : builtin_reserve_sprites, 'industry_type' : builtin_industry_type, 'int' : builtin_int, 'abs' : builtin_abs, 'acos' : builtin_trigonometric, 'asin' : builtin_trigonometric, 'atan' : builtin_trigonometric, 'cos' : builtin_trigonometric, 'sin' : builtin_trigonometric, 'tan' : builtin_trigonometric, 'UCMP' : builtin_ucmp, 'CMP' : builtin_cmp, 'rotate' : builtin_rotate, 'sound' : builtin_sound_file, 'import_sound': builtin_sound_import, 'relative_coord' : builtin_relative_coord, 'num_corners_raised' : builtin_num_corners_raised, 'slope_to_sprite_offset' : builtin_slope_to_sprite_offset, 'palette_1cc' : builtin_palette_1cc, 'palette_2cc' : builtin_palette_2cc, 'vehicle_curv_info' : builtin_vehicle_curv_info, } nml-0.2.4/nml/lz77.py0000644000061700006170000000454212036626442015004 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class LZ77(object): def __init__(self, data): self.position = 0 self.stream = data def encode(self): output = "" literal_bytes = "" while self.position < len(self.stream): overlap_len = 0 # Loop through the lookahead buffer. for i in range(3, min(len(self.stream) - self.position + 1, 16)): # Set pattern to find the longest match. pattern = self.stream[self.position:self.position+i] # Find the pattern match in the window. start_pos = max(0, self.position - (1 << 11) + 1) result = self.stream.find(pattern, start_pos, self.position) # If match failed, we've found the longest. if result < 0: break p = self.position - result overlap_len = i if overlap_len > 0: if len(literal_bytes) > 0: output += chr(len(literal_bytes)) output += literal_bytes literal_bytes = "" val = ((-overlap_len) << 3) & 0xFF | (p >> 8) if val < 0: val += 256 output += chr(val) output += chr(p & 0xFF) self.position += overlap_len else: literal_bytes += self.stream[self.position] if len(literal_bytes) == 0x80: output += chr(0) output += literal_bytes literal_bytes = "" self.position += 1 if len(literal_bytes) > 0: output += chr(len(literal_bytes)) output += literal_bytes return output nml-0.2.4/nml/tokens.py0000644000061700006170000001455412036626442015510 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import sys, re import ply.lex as lex from nml import expression, generic reserved = { 'grf' : 'GRF', 'var' : 'VARIABLE', 'param' : 'PARAMETER', 'cargotable' : 'CARGOTABLE', 'railtypetable' : 'RAILTYPETABLE', 'if' : 'IF', 'else' : 'ELSE', 'while' : 'WHILE', # reserved 'item' : 'ITEM', # action 0/3 'property' : 'PROPERTY', 'graphics' : 'GRAPHICS', 'snowline' : 'SNOWLINE', 'basecost' : 'BASECOST', 'template' : 'TEMPLATE', #sprite template for action1 'spriteset' : 'SPRITESET', #action 1 'spritegroup' : 'SPRITEGROUP', #action 2 'switch' : 'SWITCH', #deterministic varaction2 'random_switch' : 'RANDOMSWITCH', #random action2 'produce' : 'PRODUCE', #production action2 'error' : 'ERROR', #action B 'disable_item' : 'DISABLE_ITEM', 'replace' : 'REPLACESPRITE', #action A 'replacenew' : 'REPLACENEWSPRITE', #action 5 'font_glyph' : 'FONTGLYPH', #action 12 'deactivate' : 'DEACTIVATE', #action E 'town_names' : 'TOWN_NAMES', # action F 'string' : 'STRING', 'return' : 'RETURN', 'livery_override' : 'LIVERYOVERRIDE', 'exit' : 'SKIP_ALL', 'tilelayout' : 'TILELAYOUT', 'spritelayout' : 'SPRITELAYOUT', 'alternative_sprites' : 'ALT_SPRITES', 'base_graphics' : 'BASE_SPRITES', 'recolour_sprite' : 'RECOLOUR_SPRITE', 'engine_override' : 'ENGINE_OVERRIDE', 'sort' : 'SORT_VEHICLES', } line_directive1_pat = re.compile(r'\#line\s+(\d+)\s*(\r?\n|"(.*)"\r?\n)') line_directive2_pat = re.compile(r'\#\s+(\d+)\s+"(.*)"(\s+\d+\s*)?\r?\n') class NMLLexer(object): # Tokens tokens = reserved.values() + [ 'ID', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MODULO', 'AND', 'OR', 'XOR', 'LOGICAL_AND', 'LOGICAL_OR', 'LOGICAL_NOT', 'BINARY_NOT', 'EQ', 'LPAREN', 'RPAREN', 'SHIFT_LEFT', 'SHIFT_RIGHT', 'SHIFTU_RIGHT', 'COMP_EQ', 'COMP_NEQ', 'COMP_LE', 'COMP_GE', 'COMP_LT', 'COMP_GT', 'COMMA', 'RANGE', 'LBRACKET', 'RBRACKET', 'LBRACE', 'RBRACE', 'TERNARY_OPEN', 'COLON', 'SEMICOLON', 'STRING_LITERAL', 'NUMBER', 'FLOAT', 'UNIT', ] t_PLUS = r'\+' t_MINUS = r'-' t_TIMES = r'\*' t_MODULO = r'%' t_DIVIDE = r'/' t_AND = r'&' t_OR = r'\|' t_XOR = r'\^' t_LOGICAL_AND = r'&&' t_LOGICAL_OR = r'\|\|' t_LOGICAL_NOT = r'!' t_BINARY_NOT = r'~' t_EQ = r'=' t_LPAREN = r'\(' t_RPAREN = r'\)' t_SHIFT_LEFT = r'<<' t_SHIFT_RIGHT = r'>>' t_SHIFTU_RIGHT = r'>>>' t_COMP_EQ = r'==' t_COMP_NEQ = r'!=' t_COMP_LE = r'<=' t_COMP_GE = r'>=' t_COMP_LT = r'<' t_COMP_GT = r'>' t_COMMA = r',' t_RANGE = r'\.\.' t_LBRACKET = r'\[' t_RBRACKET = r'\]' t_LBRACE = r'{' t_RBRACE = r'}' t_TERNARY_OPEN = r'\?' t_COLON = r':' t_SEMICOLON = r';' def t_FLOAT(self, t): r'\d+\.\d+' t.value = expression.ConstantFloat(float(t.value), t.lineno) return t def t_NUMBER(self, t): r'(0x[0-9a-fA-F]+)|(\d+)' base = 10 if len(t.value) >= 2 and t.value[0:2] == "0x": t.value = t.value[2:] base = 16 t.value = expression.ConstantNumeric(int(t.value, base), t.lineno) return t def t_UNIT(self, t): r'(nfo)|(mph)|(km/h)|(m/s)|(hpI)|(hpM)|(hp)|(kW)|(tons)|(ton)|(kg)' return t def t_ID(self, t): r'[a-zA-Z_][a-zA-Z0-9_]*' if t.value in reserved: # Check for reserved words t.type = reserved[t.value] else: t.type = 'ID' t.value = expression.Identifier(t.value, t.lineno) return t def t_STRING_LITERAL(self, t): r'"([^"\\]|\\.)*"' t.value = expression.StringLiteral(t.value[1:-1], t.lineno) return t # Ignored characters def t_ignore_comment(self, t): r'(/\*(\n|.)*?\*/)|(//.*)' self.increment_lines(t.value.count("\n")) def t_ignore_whitespace(self, t): "[ \t\r]" pass def t_line_directive1(self, t): r'\#line\s+\d+\s*(\r?\n|".*"\r?\n)' m = line_directive1_pat.match(t.value) assert m is not None fname = self.lexer.lineno.filename if m.group(3) is None else m.group(3) self.set_position(fname, int(m.group(1), 10)) self.increment_lines(t.value.count('\n') - 1) def t_line_directive2(self, t): r'\#\s+\d+\s+".*"(\s+\d+\s*)?\r?\n' m = line_directive2_pat.match(t.value) assert m is not None self.set_position(m.group(2), int(m.group(1), 10)) self.increment_lines(t.value.count('\n') - 1) def t_newline(self, t): r'\n+' self.increment_lines(len(t.value)) def t_error(self, t): print "Illegal character '%s' at %s" % (t.value[0], t.lexer.lineno) sys.exit(1) def build(self, **kwargs): self.lexer = lex.lex(module=self, **kwargs) self.set_position('input', 1) def set_position(self, fname, line): """ @note: The lexer.lineno contains a Position object. """ self.lexer.lineno = generic.LinePosition(fname, line) def increment_lines(self, count): self.set_position(self.lexer.lineno.filename, self.lexer.lineno.line_start + count) nml-0.2.4/nml/generic.py0000644000061700006170000001121512036626442015610 0ustar abuildabuild00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- import sys def truncate_int32(value): """ Truncate the given value so it can be stored in exactly 4 bytes. The sign will be kept. Too high or too low values will be cut off, not clamped to the valid range. @param value: The value to truncate. @type value: C{int} @return: The truncated value. @rtype: C{int}. """ #source: http://www.tiac.net/~sw/2010/02/PureSalsa20/index.html return int( (value & 0x7fffFFFF) | -(value & 0x80000000) ) def check_range(value, min_value, max_value, name, pos): """ Check if a value is within a certain range and raise an error if it's not. @param value: The value to check. @type value: C{int} @param min_value: Minimum valid value. @type min_value: C{int} @param max_value: Maximum valid value. @type max_value: C{int} @param name: Name of the variable that is being tested. @type name: C{basestring} @param pos: Position information from the variable being tested. @type pos: L{Position} """ if not min_value <= value <= max_value: raise RangeError(value, min_value, max_value, name, pos) def reverse_lookup(dic, val): #reverse dictionary lookup return [k for k, v in dic.iteritems() if v == val][0] class Position(object): """ Base class representing a position in a file. @ivar filename: Name of the file. @type filename: C{str} """ def __init__(self, filename): self.filename = filename class LinePosition(Position): """ Line in a file. @ivar line_start: Line number (starting with 1) where the position starts. @type line_start: C{int} """ def __init__(self, filename, line_start): Position.__init__(self, filename) self.line_start = line_start def __str__(self): return '"%s", line %d' % (self.filename, self.line_start) class PixelPosition(Position): """ Position of a pixel (in a file with graphics). @ivar xpos: Horizontal position of the pixel. @type xpos: C{int} @ivar ypos: Vertical position of the pixel. @type ypos: C{int} """ def __init__(self, filename, xpos, ypos): Position.__init__(self, filename) self.xpos = xpos self.ypos = ypos def __str__(self): return '"%s" at [x: %d, y: %d]' % (self.filename, self.xpos, self.ypos) class ScriptError(Exception): def __init__(self, value, pos = None): self.value = value self.pos = pos def __str__(self): if self.pos is None: return self.value else: return str(self.pos) + ": " + self.value class ConstError(ScriptError): def __init__(self, pos = None): ScriptError.__init__(self, "Expected a compile-time integer constant", pos) class RangeError(ScriptError): def __init__(self, value, min_value, max_value, name, pos = None): ScriptError.__init__(self, name + " out of range " + str(min_value) + ".." + str(max_value) + ", encountered " + str(value), pos) class ImageError(ScriptError): def __init__(self, value, filename): ScriptError.__init__(self, value, 'Image file "%s"' % filename) class OnlyOnceError(ScriptError): def __init__(self, typestr, pos = None): ScriptError.__init__(self, "A grf may contain only one %s." % typestr, pos) class OnlyOnce: """ Class to enforce that certain objects / constructs appear only once. """ seen = {} @classmethod def enforce(cls, obj, typestr): """ If this method is called more than once for an object of the exact same class, an OnlyOnceError is raised. """ objtype = obj.__class__ if objtype in cls.seen: raise OnlyOnceError(typestr, obj.pos) cls.seen[objtype] = None @classmethod def clear(cls): cls.seen = {} def print_warning(msg, pos = None): """ Output a warning message to the user. """ if pos: print >> sys.stderr, str(pos) + ":", print >> sys.stderr, msg nml-0.2.4/Makefile0000644000061700006170000000034412036626441014474 0ustar abuildabuild00000000000000MAKE?=make PYTHON?=python .PHONY: regression install bundle clean regression: $(MAKE) -C regression test: regression install: $(PYTHON) setup.py install bundle: $(PYTHON) bootstrap.py clean: $(MAKE) -C regression clean nml-0.2.4/examples/0000755000061700006170000000000012036626604014652 5ustar abuildabuild00000000000000nml-0.2.4/examples/object/0000755000061700006170000000000012036626604016120 5ustar abuildabuild00000000000000nml-0.2.4/examples/object/example_object.nml0000644000061700006170000001275112036626441021616 0ustar abuildabuild00000000000000/* * This file is aimed to provide an example on how to code an Object in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * The NewGRF implements a replacement for the company land as NewObject which does * not show a sign but blends in with the terrain naturally. In transparent view * it shows a company-coloured border around the tiles. * * Apart from this file, you will also need the following * - Graphics, found in cc_grid.png (in the same folder) * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the second real grf defined as part of * NML (the first is the train example), therefore the last character is * set to 1. Successive grfs will have 2, 3, etc. there, to make sure each * example grf has a unique GRFID. */ grfid: "NML\01"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESCRIPTION); /* This is the first version, start numbering at 0. */ version: 0; min_compatible_version: 0; /* This NewGRF has no parameters. See the train example NewGRF for parameter * usage */ } /* Using parametrized sprite layouts are only valid in OpenTTD r22723 or later. * Earlier versions will choke on those and otherwise disable the NewGRF. */ if (version_openttd(1,2,0,22723) > openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_VERSION_22723)); } // Template for 19 sprites: one for each possible tile slope template tmpl_groundsprites(x, y) { [ 0+x, y, 64, 31, -31, 0 ] [ 80+x, y, 64, 31, -31, 0 ] [ 160+x, y, 64, 23, -31, 0 ] [ 240+x, y, 64, 23, -31, 0 ] [ 320+x, y, 64, 31, -31, 0 ] [ 398+x, y, 64, 31, -31, 0 ] [ 478+x, y, 64, 23, -31, 0 ] [ 558+x, y, 64, 23, -31, 0 ] [ 638+x, y, 64, 39, -31, -8 ] [ 718+x, y, 64, 39, -31, -8 ] [ 798+x, y, 64, 31, -31, -8 ] [ 878+x, y, 64, 31, -31, -8 ] [ 958+x, y, 64, 39, -31, -8 ] [1038+x, y, 64, 39, -31, -8 ] [1118+x, y, 64, 31, -31, -8 ] [1196+x, y, 64, 47, -31,-16 ] [1276+x, y, 64, 15, -31, 0 ] [1356+x, y, 64, 31, -31, -8 ] [1436+x, y, 64, 31, -31, -8 ] } /* Spriteset of the 19 possible landslopes with company-coloured grid */ spriteset (cc_frame, "cc_grid.png") { tmpl_groundsprites(1, 1) } spritelayout company_land_layout { ground { /* normal ground sprite - always draw */ sprite: LOAD_TEMP(0) + LOAD_TEMP(1); } childsprite { /* company-coloured border - always draw */ sprite: cc_frame(LOAD_TEMP(0)); always_draw: 1; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { /* again the normal ground sprite. Thus in non-transparent view * only the normal ground sprite is shown. In transparent view * this acts as sprite which darkens the other two sprites via * a translation to transparency. */ sprite: LOAD_TEMP(0) + LOAD_TEMP(1); } } /* A pseudo-switch which sets the temporary parameters for the sprite layout */ switch (FEAT_OBJECTS, SELF, company_land_terrain_switch, [ /* We store the offset into the spriteset due to the tile slope into the 1st temporary variable * (= storage register 0) */ STORE_TEMP(slope_to_sprite_offset(tile_slope), 0), /* We store the offset to the flat groundsprite we use into the 2nd temporary variable * (= storage register 1) */ STORE_TEMP(GROUNDSPRITE_NORMAL, 1), STORE_TEMP(terrain_type == TILETYPE_DESERT ? GROUNDSPRITE_DESERT : LOAD_TEMP(1), 1), STORE_TEMP(terrain_type == TILETYPE_SNOW ? GROUNDSPRITE_SNOW : LOAD_TEMP(1), 1), ]) { company_land_layout; } /* Pseudo switch for the purchase list branch: we want to display the flat ground tile */ switch (FEAT_OBJECTS, SELF, company_land_purchase_switch, [ STORE_TEMP(0, 0), STORE_TEMP(GROUNDSPRITE_NORMAL, 1), 1 ]) { company_land_layout; } /* Define the object itself */ item(FEAT_OBJECTS, company_land) { property { /* The class allows to sort objects into categories. This is 'infrastructure' */ class: "INFR"; /* If no other NewGRF provides this class before us, we have to name it */ classname: string(STR_NAME_OBJCLASS_INFRASTRUCTURE); /* Name of this particular object */ name: string(STR_NAME_COMPANY_LAND); climates_available: ALL_CLIMATES; size: [1, 1]; build_cost_multiplier: 1; remove_cost_multiplier: 1; introduction_date: date(1,1,1); // available from day 1 end_of_life_date: date(10000,1,1); // available till year 10000 /* Anything can overbuild the object, removing returns the money, we don't want foundations and we want to allow bridges */ object_flags: bitmask(OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_REMOVE_IS_INCOME, OBJ_FLAG_NO_FOUNDATIONS, OBJ_FLAG_ALLOW_BRIDGE); height: 0; // it's only a ground tile num_views: 1; } graphics { purchase: company_land_purchase_switch; tile_check: return 0; additional_text: return string(STR_NAME_COMPANY_LAND); company_land_terrain_switch; } } nml-0.2.4/examples/object/cc_grid.png0000644000061700006170000000613412036626441020223 0ustar abuildabuild00000000000000PNG  IHDR1KsRGBPLTE 000@@@PPPdddttt4)ߙz &(|ͧK|W u ]ߜM4z{jCHE_7нmC21LPԄg|ԣkCG[_h 7W[wFװ΀@R]N 7N6 o{sK_w+萆tIj.zl`C'ӆFV>^) 6,>7нY/^]?}T %w|Ak-mftYɧob&|â /ѻ0z#{th_ vW=μd^[ sI k71ц ڻ$4B{yILD>{unaw }Z쳞T^j}m(|qeΈ"#Ļ'lpw+BR0H@}YmnW`[_vJEy-y~Z (jB:|LJeTn뽲L *L; n#J6MfBj/G J(>7R*Es:[%_PljOcg+m.s=\~-}й<m/ݣmlq n55hu1 @Ƚ%Ai2I{~o>Nhq)^,g(r!S'>)pyV&|ԪѡJLt&%ttL&zGϮPUODLÆM-KgP})25I2ϊBcѽNx4Yv'Tr<Xo-_K|dd˳.ઋ(c.~_л|vS﫱%%째E`=}/ɥ$(QU lPfv7}n+H3e`LU%yE ^w1{uy5VJ~7FVmzʍ9.rT]d.s659^ӻ3Yr-26m<&br U.vHzWI' ucTƛ: [i?u^YxzCȐb+%F`)$SS>o6VQ9dl02_~r^cRmŏg_Gd3:ZѳXz_T:9 [VXrp=8v kbgwaՃ2`|Owx3nw߇YDpuIM[b>d3ѻ . NB'6l( R? *wV~=:s&=㈔2jho2gz s-s&m~FtdEW !އFoٲeMYO{e˖oCp[av!IENDB`nml-0.2.4/examples/object/lang/0000755000061700006170000000000012036626604017041 5ustar abuildabuild00000000000000nml-0.2.4/examples/object/lang/english.lng0000644000061700006170000000131412036626441021172 0ustar abuildabuild00000000000000##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Object STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Object{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}planetmaker, {BLACK}coding by {SILVER}planetmaker.{}{BLACK}This NewGRF defines a tile which can act as company-land replacement. STR_VERSION_22723 :1.2.0 (r22723) STR_NAME_OBJCLASS_INFRASTRUCTURE :Infrastructure STR_NAME_COMPANY_LAND :Company land nml-0.2.4/examples/train/0000755000061700006170000000000012036626604015767 5ustar abuildabuild00000000000000nml-0.2.4/examples/train/icm.png0000644000061700006170000000775212036626441017257 0ustar abuildabuild00000000000000PNG  IHDR`;;sRGBgAMA aPLTE4I=#0‘W% ˃e%K:un+9UYh `=A)7 9w{z2󺪨  ,b`kDN L8\|],u6ɲD(醀}oqR9d=fJ,USU%K~o#OO%t2X 񲪶~W-x"C>>]]]^@9 QvVo.a WOÖ5T"f. @'1.ʨ4pG=[ɱ{Xӕp '1_/ݖ?oCm"=stכ1/mh]FMch 6su 2|}ٽ; >pmOC;?"vtx_ݚͷټz*Mzg3YMcK?k* @!-<Ϊ.*{nFv <~;j9ohU?s+_>@Z[f#F,p2#co&N\8{g>A|!? ٱ(Vծv6dwcإ=oZju;$ !zT8o&B ? >TOM^??w~PGEwtd7 bB)6qG.~P.:~x^  D :@MF!nw[CS?(~E?5,<da~QA roI^Tt!0DbI?r~P~0I4$Aj(`X+o+2VAZykGP.UanGb= `'KY q!e0e0ܡn?&~N6G;J/>scE煏A s8t[o]?8~p2~~pX?Hގ R 8bA@ |;A$S/Ez ?:A?}`< s᳨A5KVV^d~Z?(LxkZrIe>?g00QWwhS/A̍ n(q'tBfl8CC7 jwEJ%oK)k2L ȕ%K ~Y@.)зKmgeg2C:'YVd}viiwK%.-.6viawK%[@.)гKuwSaA9xZ Ɠq ["n-rz( $)S5M'=s.ZU}VeWЪ|6|o'W;?2.˚5vn0SgG(̱<@;>쉷gAw'\])5简er@jl^s .s< qltUkY4qeYRD %CXS|݂r@v4|Js@G.~ʁ^-4.%I|χ=,uYr>ȾbhRnGy&o$a tm^q !JQǴ÷}3> rrgʼA}s $ZkHm1"9*8Z]6hsr;j6n8SIGX)C(qA=>@ŮJG6j%N|' {j[w)TRPי>XS49 / b>!1\cc6e8+~ qp'N9i_ Miw Zprprɗ߮)qLQ+bDA9Z=+. P>r[`[<|㷣V%v,rww.N X\tS%fq}n"9`#h,jG٠  ek΁TH_SE#}q}ŁT ]_9xK:htg_W65]\Ƶq\hv8pXbUA;St-.cj/]L<9|79ug@mp?u=~]5ܣ\.ə6 _pnz@qW "gm>\16B s73AtϰJbj;PYt{Eh5]vϿr@E\BN0ށ̦6%VzۃƷ) *IA?}ku'|+H ]۳?ʁ: ^v%Fp>dm<SBȫr۴oDYVЩNҮ 3ZAǐ|?hw{g2@9:WtowN9Pf|/`|~BO4/Sՠxzt%UgRÙMR(=9fkS|*o UQ=0J;/h=K_aXTN‡yzI8/y7rP}2h&qw*jFu1IENDB`nml-0.2.4/examples/road_vehicle/gfx/flatbed_truck_1_paper.png0000644000061700006170000000407712036626441025017 0ustar abuildabuild00000000000000PNG  IHDR^<sRGBPLTE4'8npXȽʍtҸup+‹\⳪)E":h"9/9Rh ݘ`{9X~U&]Ue$9Ar6/ __|[ &!<?uYx.V}X¦\rp|Àq\Hu>?k$ҡN7m3I @P%>(.^Z#V#d7 /3'c%88R0l.+?]d9]G0 )aZ+nn;H'G1]_ZN6|$m+Ƨ&? = AeNxxՖ`:U`8 |?b!yB)KUV m70IU Ge׫t C%HDF6' Zp&hݎX1~Cɤ8>U H4@?rp8ro)O+)/(aŗ@P ڰ*UcW\v1lP,r"0!?|sPhl @}?Y䀳=U‡5B2Fh?XY@uVKq0n8āsj|דQ-8PV-ZªR[9i|bV_ND zՍ, dSܗÓ9SlFb]<SBĻ) SA|f wq-S~Px$VCTx*<ވmŷuIޗ渙)r2'kB|%T<_9\ Ş 3˨_Q{r( ̖+h¿zsaK`R~<Ml'|wٯ%{(z%"}p-O}?q"{3+} *xi;I3>՟j/DR}78}%ws`oăk݃{ Gx'ӷN}챋S< G=pO@L豌ܧRު/Tx@mɑ. O<%f|/m@*#6 p }s !H҂(x*<G=l?DzǏ;} Kw+as* ,+% 07OSx$$ApқOOfqgosT'yP8BOC[0zC; (Kvz<"".Nշ@e q-0!?QЏP<A齙-?GxE|YkDAq?q]<~-O}d}gn yhο?ݬj #YE0-͙@}kAM tRy5CEenJHM f\X/̧7lkai N4X,d]Ɵ(Bj\5.UtK\NGߗL乺[Ti[ v<)Zp#V_SdՇ>)NzkȽlS2|_` wRI^Л E^5fCecP]B?6x2[ XֆYob}~ɜG)bmuCĪ_V#҈W\ ·UOeA2)Ҁߩ⏼R?1KNx 6 P>y(zh4E:?@l]ma͡Qh Z,:Btfpl mGipQ *_]x=m/)?mApH/BE:% 7Ʒ) *+‰H7Z=@ ;Q@S\" =$uӓ=;x౉woۭ"J 3P|N)zhl432R@Tx*xlozHޗᴙ%?eTO=A& 9Z͎>3_g=9{OŖ3h4+% ~vi]K^&4aeH4õ<5ȸ}'i^~Հ fOIENDB`nml-0.2.4/examples/road_vehicle/gfx/flatbed_truck_1_copper.png0000644000061700006170000000406212036626441025172 0ustar abuildabuild00000000000000PNG  IHDR^<sRGBPLTE4(23E~h@SžAXf mU]oUSg4j!jPh?qÛOL֘ċ-ҢK^睛Cl֊LZ.5r /5X/Y v?kŤ,%Ӑl%r[N֐EbKhKSAud#,|AN<QNg^.50O4uj4lk0oċ)6q!~`hm035>([zX)>Vxx8ITD zB2\N483|?% K%P~t(֣OkhjS"Wj/z[k"3>]hAj例T+BlxU$ޕ9RbuGN#҄˟3l%~EeR5%yj\v1lP-r"(á?y(zh4G:?@bfqaJ8Qa#>X9@wOhsj;Kj@gipm_iT퀟v E<\"NiRAuxhD6ޡ""DzՃ߬ dls484z()'{v KO cޮD+T|Nd2 ; hSF('+z_f| ;iPP ja8ά~EH~>-Wq 4+υQ?PN;4/xI0_K$PNE[iI? G)9[[IENDB`nml-0.2.4/examples/road_vehicle/lang/0000755000061700006170000000000012036626604020217 5ustar abuildabuild00000000000000nml-0.2.4/examples/road_vehicle/lang/english.lng0000644000061700006170000000124312036626441022351 0ustar abuildabuild00000000000000##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Road Vehicle STR_GRF_DESC :{ORANGE}NML Example NewGRF: Road Vehicle{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}DanMack, Zephyris, {BLACK}coding by {SILVER}Terkhen, planetmaker.{}{BLACK}This NewGRF defines first-generation flatbed truck. STR_ERROR_ENGINE_POOL :enable multiple NewGRF engine sets = on STR_NAME_FLATBED_TRUCK_1 :Flatbed Truck MkI nml-0.2.4/examples/road_vehicle/example_road_vehicle.nml0000644000061700006170000001702112036626441024145 0ustar abuildabuild00000000000000/* * This file is aimed to provide an example on how to code a vehicle in NML * In this case a road vehicle is coded, other vehicle types work in a similar fashion. * To keep the code readable, not every property or variable is documented in * detail, refer to the vehicle-specific reference in the documentation. * The coded vehicle is relatively simple. Apart from the minimum required, * it has cargo-specific capacaity and graphics. For a more advanced example, * refer to the train example. * * The vehicle coded here is a first-generation flatbed truck. * It is taken from OpenGFX+ Road Vehicles, with some modifications * to provide a better example. Original graphics are by DanMack and Zephyris, * coding by Terkhen and planetmaker. * * Apart from this file, you will also need the following * - Graphics, found in 'gfx' folder. * - Language files, to be placed in the 'lang' folder. * Currently only english.lng is supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. Additionally, * user-configurable parameters are defined here also. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the fourth real grf defined as part of * NML, therefore the last character is set to 3 as numbering is zero-based. * Successive grfs will have 4, 5, etc., to make sure each example grf has * a unique GRFID. */ grfid: "NML\03"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESC); /* This is the first version, start numbering at 0. */ version: 0; min_compatible_version: 0; } /* Check for engine pool */ if (!dynamic_engines) { error(ERROR, USED_WITH, string(STR_ERROR_ENGINE_POOL)); } /* Define a cargo translation table * All cargo types that need any special treatment must be included here * Add cargos used in the refit mask first, as those are limited to 32 bits * so these cargos must be within the first 32 entries */ cargotable { /* Used in refit mask */ LVST, // Livestock (piece goods) WOOL, // Wool (piece goods, covered) SCRP, // Scrap metal (bulk) FICR, // Fibre crops (bulk, piece goods) PETR, // Petrol / fuel oil (liquid) RFPR, // Refined products (liquid) /* Not used in refit mask */ GOOD, // Goods (express) ENSP, // Engineering supplies (express, piece goods) FMSP, // Farm supplies (express, piece goods) MNSP, // Manufacturing supplies (express, piece goods) PAPR, // Paper (piece goods) STEL, // Steel (piece goods) VEHI, // Vehicles (piece goods, oversized) COPR, // Copper (piece goods) WOOD, // Wood (piece goods) } /* Sprite template for a truck */ template tmpl_truck(x) { [ 0 + x, 0, 8, 18, -3, -10] [ 16 + x, 0, 20, 16, -14, -7] [ 48 + x, 0, 28, 12, -14, -6] [ 96 + x, 0, 20, 16, -6, -7] [ 128 + x, 0, 8, 18, -3, -10] [ 144 + x, 0, 20, 16, -14, -7] [ 176 + x, 0, 28, 12, -14, -6] [ 224 + x, 0, 20, 16, -6, -7] } /* Define various cargo-specific graphics */ /* Paper */ spriteset(flatbed_truck_1_paper_empty, "gfx/flatbed_truck_1_paper.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_paper_full, "gfx/flatbed_truck_1_paper.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_paper { loaded: [flatbed_truck_1_paper_empty, flatbed_truck_1_paper_full]; loading: [flatbed_truck_1_paper_empty, flatbed_truck_1_paper_full]; } /* Steel */ spriteset(flatbed_truck_1_steel_empty, "gfx/flatbed_truck_1_steel.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_steel_full, "gfx/flatbed_truck_1_steel.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_steel { loaded: [flatbed_truck_1_steel_empty, flatbed_truck_1_steel_full]; loading: [flatbed_truck_1_steel_empty, flatbed_truck_1_steel_full]; } /* Wood */ spriteset(flatbed_truck_1_wood_empty, "gfx/flatbed_truck_1_wood.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_wood_full, "gfx/flatbed_truck_1_wood.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_wood { loaded: [flatbed_truck_1_wood_empty, flatbed_truck_1_wood_full]; loading: [flatbed_truck_1_wood_empty, flatbed_truck_1_wood_full]; } /* Copper */ spriteset(flatbed_truck_1_copper_empty, "gfx/flatbed_truck_1_copper.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_copper_full, "gfx/flatbed_truck_1_copper.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_copper { loaded: [flatbed_truck_1_copper_empty, flatbed_truck_1_copper_full]; loading: [flatbed_truck_1_copper_empty, flatbed_truck_1_copper_full]; } /* Goods */ spriteset(flatbed_truck_1_goods_empty, "gfx/flatbed_truck_1_goods.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_goods_full, "gfx/flatbed_truck_1_goods.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_goods { loaded: [flatbed_truck_1_goods_empty, flatbed_truck_1_goods_full]; loading: [flatbed_truck_1_goods_empty, flatbed_truck_1_goods_full]; } /* Decide capacity based on cargo type (cargo_capacity callback) * cargo_type_in_veh is available in the purchase list also, * so a separate CB for in the purchase list is not needed */ switch (FEAT_ROADVEHS, SELF, flatbed_truck_1_capacity_switch, cargo_type_in_veh) { GOOD: return 14; ENSP: return 14; FMSP: return 14; MNSP: return 14; PAPR: return 15; PETR: return 10; RFPR: return 10; STEL: return 15; VEHI: return 12; return 20; } /* Define the road vehicle */ item(FEAT_ROADVEHS, flatbed_truck_1) { property { /* Properties common to all vehicle types */ name: string(STR_NAME_FLATBED_TRUCK_1); climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); introduction_date: date(1926,01,01); model_life: 65; /* retire_early not set, use default retirement behaviour */ vehicle_life: 15; reliability_decay: 20; refittable_cargo_classes: bitmask(CC_PIECE_GOODS, CC_EXPRESS); non_refittable_cargo_classes: bitmask(CC_PASSENGERS, CC_REFRIGERATED); /* Livestock, wool and fibre crops can be transported acc. cargo classes * But we don't want that, so disable refitting for them. * Scrap metal, petrol and refined products cannot be transported acc. cargo classes * We do want to transport those, so enable refitting for them. */ refittable_cargo_types: bitmask(LVST, WOOL, SCRP, FICR, PETR, RFPR); loading_speed: 5; cost_factor: 108; running_cost_factor: 90; /* cargo_age_period is left at default */ /* RV-specific properties */ sprite_id: SPRITE_ID_NEW_ROADVEH; speed: 48 km/h; misc_flags: bitmask(ROADVEH_FLAG_2CC); refit_cost: 0; // Refitting is free /* callback_flags are not set, no need to manually enable callbacks */ running_cost_base: RUNNING_COST_ROADVEH; power: 120 hp; weight: 9.5 ton; /* TE and air drag coefficient is left at default */ cargo_capacity: 20; // Changed by callback sound_effect: SOUND_BUS_START_PULL_AWAY; /* Visual effect is left at default (no effect) */ } /* Define graphics for various cargo types, as well as the capacity callback */ graphics { cargo_capacity: flatbed_truck_1_capacity_switch; PAPR: flatbed_truck_1_paper; STEL: flatbed_truck_1_steel; COPR: flatbed_truck_1_copper; WOOD: flatbed_truck_1_wood; default: flatbed_truck_1_goods; // Default to Goods. } } nml-0.2.4/examples/railtype/0000755000061700006170000000000012036626604016503 5ustar abuildabuild00000000000000nml-0.2.4/examples/railtype/gfx/0000755000061700006170000000000012036626604017267 5ustar abuildabuild00000000000000nml-0.2.4/examples/railtype/gfx/depot_electric.png0000644000061700006170000000647012036626441022770 0ustar abuildabuild00000000000000PNG  IHDRf7sRGBPLTE4bn"B RD$(@/}2(t.BOz G*je(Y2DRmNQ3v<^T4fwiQQG,PEAU*/bx ހJjK$؎ t ڹhƿ!oI#B4'BLxAFJ9ȹ)7Y :? ^#$ZXw$櫛*5gvVޕJ3ȍsEI y_|^܀%Q.yn(>vD+3 WZ2a}N1i 1) 5q]V4d%p1!VJn(MQ/>&P|̑d!kB0(ڑ!d02aH,G+^QNn(>.WQ _1Pc6N"2,~ ް$fQR>\7@L N `D;АzU+M vkY$d}߅'a YdFuc{VP)C%L6`>")!O'ҊF Gn*X N;q(3YsDÐCy3 )s] „<8P푋fF9(~Y~y*N!J3L͞ $lI~}Bkǣ% *lDhkԑRkD] u rFYlx?ޮWj OE62?0TyX//6Cvi+?YaV'$#Ds-x[?#(VnpGO ~nQ`}[ȸ@Z;2_&Ai*9v'y֮H1<ߟ&> s'Ќ@G9O Xdu HF+态zqj7Mwљq t0|mc񀁚rrdgEz9?A3i_h-$ $:7~r05k?ùAi TFMHOuMKVC fDȔsRaep^QHiA7:*)45jXo" 9a^TIr -nCE8!f1+?B%Pw/m o@EZN}>P!a}t*E*6d.8I[ITWn7"$uS9PJ'Q7Us *(P!rpI؇.8ZD9i)Pd ,ϢSn}EnzN,< .pc~,>}~ĂX bA, `b6L "ފa311lxK&Moİ⭙VG,&bb(&bb(& C11 XL PL A,&714޻fĠ21 /014D>bobܜatfL KM |F;3 L A&8sc&:!ؐh%BQJŖZ{*1ggM =TT?_kbp;74Yڞ?71 3 w E^kb+s@PoJqn!I'*%A͚NM213<.Wk8-v'ĀpMِ~pZ QCzlb d0j4W71jTH=.1[dJY5L u L KLU/ e]}Q':&#9IA; 9PRQj%CiΆ6zWSs\9uob}1sRF{i6s'?pz&}wwW[Y|!51 =[11M /71C11ĂX bA, ĂX bA, _s .fIENDB`nml-0.2.4/examples/railtype/gfx/fences.png0000644000061700006170000000354312036626441021244 0ustar abuildabuild00000000000000PNG  IHDR0(LmsRGBgAMA aPLTE4SsNm4),@=4ΙyH@SSs`j9'rhUvbD*#@Y*> &Fs14۶9jй{'v wlLNM&124qΉT5wI!gѼ DhpO-9n 3S!Mch.ݙ@C85 chhgvM4tѡsNm\Q53;&rjY4Egk&tf' E㟕a>לS\SM5șE:,8f'k&ڄj k*i4ScT?lsM43ԲhJO )ҙ]A;h?rSh.HisM?tL-O/D]T˸R6~v4"t!6v4Ω5EBԪTo3  Zxɕ_¯WcĹ@qM6Uͷkn)qG]ʱxh:vaGAGѰxlEohaCԪ@G*l VZi5/귣y,yl;xj:Fh?MW3ܞIENDB`nml-0.2.4/examples/railtype/gfx/rails_overlays.png0000644000061700006170000001676412036626441023050 0ustar abuildabuild00000000000000PNG  IHDR(sRGBPLTE4 +WT X*#JVqTIi,e~~`Azf+8hWXą28I5¯2xXZJk(Ve%gKQ[ V9GQD}5?6wC]_XH\K.?Xd\k~*@V@5`S-.3ӮR= X*IoH3ɁƋ|( 뿃{:VWE> ` aE sg`$U2A?/i_P҅9fFGX 4?ҺZ'?k\yw0w sUy|>x$.Ib*Z;=IVj*떲רP^O0zIwYnBGU$LkVš.Ԃ^d8t NP[[e/(>krQZ풨Md0JB mxeU'q-BO> ,fծwcuGiU˫jTSn`At|֊&;}׹ 횔Ek(}O`*6r/nw]K]* 7Z7x!'jY]#F0Og3+h*˞ViLa+fRB"^H1(Jӻ< bErP"@s4)P Sb~?RY똲YQYgU*sRkf/(9TG"<+QF`D|hc e|KMQ&۹s](K&rhP4fƚ)2ْyVEԴ֮=\kXd'\@b(U)XB~<1YeJ̽v, W1M<ԠcAT LZ`Z#\)ӫ"Ϗ`Xp pXVpXVpyDXVw5Y}`ݤ~R)Y;<ѪN)_= JB=X"âmoQ@8nR#O)Pm{6A ,h#d%cz Û! vU om ZLAZ,oSIz7 K4 FE)#2S`;]kŒ(B|}&WU93^:βh+'9J0MncҒ؅Oj,LT%)(]d8"J@ 49.UӬJUz-JK2ʄ5LC(C 1'7>*ɇ,xQR$G|fSJmgPT6`U-GY#Jk˫wR(hoCC }XnY(KikM0& R՚/T. _k2͂&G A0M-r "R4 }ѢCwmIRLFiMrQ`*Y>K,Y6"e1Ä5LS/@;U5 vPhjiYШ!BSx>c[(*mgBvРQ*+5̺qV0}B#b C`\a)2g26l"Ϫ,۔4'+ݺ+,k0x1j`@R\(TR$cMa4\ `uX?coNe B=]fcty}?)PwEaEG"-:2ԫrgʭ[ J(e C[~5j` a*]Ư ^>-dlKX`0GEQa UaaTF6T7kMÙbA 5yBQ!%_i yȂ]X[ Cb3ߘª 7B1VaYBKR\2ۣ0rh#5Ed8=ns,IHrzfP- ̪YQ\6E>]˧Feè/,7-09 `.N[¢KQ!Or O?7n0B` E6b?Mx9A\Π.T5dS85!XqQHFˈRwQPXd yk+r949V,R.oF 2I sI.h0; `Rpb:SP(V9{It=3re\6* KYz/n nb?(7P7Ň&)BߋxR-:LX|{Xςucak%lfWa c 0Ea`;=lC-3ūEwJ桉m1- iQO#{*!LWXF z ZI'UzC_vUe 2kHRYAΈ¯uQHH@ug2dBp"P~5.ұ(FW)k)؛؃P%Xէa\9ri+= *Q+ @N Y95qu_)5Sqz㤳]W}el&  '<.pr|W]Eㅫ<.IP$ }*"O<.A|W.|}e[Z VY pXVpXVpF ~`Xΰp 78u,@;PX_ZLq9K :kch(g;ªUX&D^es1r;R*2 al)X588aDf+eBaQe2cz]uLOyWA{0,P/ice^ dlQ/#oȾ\CGX4EJO X $ks5maim*zjڭaԺ27O{WX$ K#f`!͵JHLwO}UE P>z+؏1ߵv]kRá#+ AI\ea@Bn>!Ta~њX\Hf2ﴊNc  i*kV܏ K>Wl'DvN zН7E|jch*AT`ݩ訆Q!3h*ѾyR|>A>l QE%,`i `6Xi(ްh _l1Jƶ'gl^}fqŵVJEyV"WY۲RO(?Y|Tu7yUU*ЙFORp[I4hMEY9ʼV0(5ܮ:* ( (X$=g"e#(HGњAL<cU( A,Ŭo򪢎 EbX~?ܵk1Y7s, ZTDw`]ʸ#g3rɳnAO`iXw>kqwgJkn_~Af`M]eX`duXs֭Zg-gkZ'zd>kgyj]rUkjzg*GXv¢m%j NL\BX pX pX pX pX pX pm6_V`XV*Qawq_V+5bQF~!3ݢ D8[,U}U#O' ׷oKfQCA[ҬTvKD@5YiE'kz4! _=ycUz|NkXani'bN\5gpt*6' >a̻’7^嚌!Z7=K, vD 6]i5u@e`1U07H׬($u M֤\,j ++vW0)`S4jhM"jMo # X\kIGfAʹ4?","C*~(0)?gQk&{,&V0xrt laEeΤ6oK5?zy7[ '5]T5\eKE^@9bZthY:&0(0`Ϫd Kf` I]ǃ4՚ a٢E g۞Lᙓ/gX7V{EUG?{nV\;)LoXsZ.g܏ 7K9U `iyG ?X.Q{bF۾8EEeJZ,%^"Fr/!bzXDLa|-RlWssOFѸJȺ+2-,ը5 j3QcWXuTJ+tr>b/XMV,WFjSr3751Ͷ[yXo|: ϯO`݌+ =Xc߱` }*zY)X ܡkEXL5.ٚEiBб y@p]Vwk eKqjo7uUSj֚``|mJ` .U.Z`maqq,!۷Xok sKEUб[G[`mVT+X"eXہUyWvrjkq\:dc~jM*׬FwyW]A6a궪fAZe06V; 3śKG KzkrSX`+ +\ W`+ W`+ W`+ W`+ W`kEPcmIENDB`nml-0.2.4/examples/railtype/gfx/gui_erail.png0000644000061700006170000000761712036626441021747 0ustar abuildabuild00000000000000PNG  IHDRXdmsRGBPLTE4d-=VF"GK\|}z>{)`I=\%vt< ާ6XqUBe(.CW֒aǗRbd,[2RX#Bu0ED=0..g+9)eʏ\'鞂ʸڅq0rǂ6(X)V* cÄ8e|Ů9:9K_yABcVb-xt/g@d$a:97\e&W{; Py^,[2XA!c~AS N äc cA֑Z,/H>n^ "+G ,z~Bv:RX3i&/De&UN*/{#**QcJU; O+L5&A,J'si2`A/_zhr +»v|{a:NCWQS +-7KU zb'w!{#:} ,̕BW#/)y>dF<7 "U9K i/Π;'̺=ƉmhRMnN)څqj'2l ̟,B #!E2tgMɒ՞Z1QI, PykcʲboJKJkžLRZQ]wz/C\])ƣ 'yl6x @%\7'{ar&o\hbͣ,n(kUyI>GQ%z,GX9X,[)u2+.OASe6`/e XUpX/`<6m>^<̠JN&_wwX\Aո<2'A*{XIR|S!r-u4y5LR3zU5ZKmQn}Uh}Y"4tɥ}+_,D,,VFy ) 5V#$WO[@ZNJkVKs`!ʱKO, :LRr9z?AyU R'O>ʴ|$@QvJz+ܵAWOE["GZNJ݅h`m {dTYI%'V2ᦡbmz¨ͣ XJz+WFJA1(|  KjԻ-J IҧOe?J=(KBJKL1<05n.%f S)*s ':)ҋE))ƊyPR|E*kkO ,#9P:*(R)AJ&2!"N*kk7=~4ĊŃFC&&=H)bF_R/)Z"' ?IRB1=()`EhHhǤnCrـU?]H +).ƊED5 UYk)D ?鷐FX I)`-+R|%PIPerXE(aԒSRwBcEzHC=rL=Z=Pat%tv "5Vci_9HUvK\{F`O.,LJSU -J|u~*7*„ [s`j?U)I6lsϘ)ڛLe,6,R|ρ V1_QVe,VI|>q`F%`c,6bc,6&edVaVD;ȏ:Z.oIF~lП9Z;/oݓț)F~K:됝u=Q$ƀuwN*[ãvIoDޜu;c/[:m͓N0#g,+H2@}(}IokDޠu,_>TKn>$&eӋ=j~3'7 =`U,c8a"?__w:M[rƪsy2֩E~hlT+;D'U$&eӋ WhVn]NJiMXE|Z> kAJvGEd7$kuzIe)Dk=򽉯4X//%d֣ 3$fտ@ւe;8gXybga`ZkZ7G T|) s+UjGq`),Qu E1 (6,@~Ue]v_tKN+`Q`=; E|~?$2X 3d]W#~yYBI|e,:R e<1v_,y`1X(2X &Z{_,^W*b;Iy ,6bcc,6bc,66bc,6bcc,6bc,66bc,6< -yIENDB`nml-0.2.4/examples/railtype/gfx/gui_rail.png0000644000061700006170000000666312036626441021602 0ustar abuildabuild00000000000000PNG  IHDRXdmsRGBPLTE4 Z~C<$[Q x߽#9B.`¥q5tr2=N'1qW*Ԁ\dsV݀^'s`/+F t}wO$r^/8k*>(=Yq JVSP@PkҽVҊ7",䷠ي#"7|66CyJ!Y9VZu8ީɻ3NAXJ} Xj!aOl/=%s"XMrGob4tcuuupj5d+b#O~kp{ +'jB8McIjO:ZC6Bn ѷѳo X>vYq Cl YpxryO޿ťB8Uc!K>j|Av)O)%-Cؽk/á+rG#qXhPsq:uXS*O:+["+Vj0]wXF88`{Wr~ȯðF՟tZA09Q+yҕVnk-Ka_<w PtJ~~rԺmXMּ1ZQm6lZ39C4_sSr֠s}4sB<@sX 1JgP)AZ jʸ%0 W9+tesu `m@ "d阒j0z1|5dEHW2W]Ւ8Ĥ"*kjB=j0yF A|gL#]YVvt2(}1@+ =V#X Wc$_C®4`UHcԶS[WfR ౚ %i=֤Kt]9chK!iگ ÑK9hr®PƨE7`«yFF+u ]Y{`k"Mh#tXj X.#|8ʕ7&2fѓu`~ Ia:?+{l=Vz&H;宪D1fW&=<& Wrkre/#ZHwe6c Zt+`1XyJrkG,< `qp0X &Uߵ;%YL魼ykjdKOH`U݉=`5N88;FV~ 8IRCjo+V՝ۣXM܊^ [(Vŝ[X ̊t \Vmŝ[X `%U|9mۤXͫ=l~KVB`Uމ5`5N:w&KcubU؉eհ;G'%VՑUe'v)V*?LZZ]]VۤXͫ2h)pXw;obe+|h j(d^bWTu[u6z{ZOsV妷+UyE5PPl{}FtfSTu{[bW#6h!X֭L'4׋ zo5`ĩ#>`LX`1X WFFbXFbXo+V}+`iBTWdBece2X WJU`1X)3_,+ӺWFM+`Ъ|e8;O`q0X `qp0X `q0X `q0X `qp0X `q0X `q|?Q4IENDB`nml-0.2.4/examples/railtype/gfx/depot_normal.png0000644000061700006170000000630012036626441022456 0ustar abuildabuild00000000000000PNG  IHDRf7sRGBPLTE4(boD&h!.v`l(XEDM3I/ ,D!QFheOuU G**:jQ!=hG ?@;Z_V]#*vʨQ3TQzs ?bAIh,E%K]$unPR8I1N_(NE@PrKQhabģ;2s SS]TL,XO2qV1!Ic%JOb Eg7 %*=$4T1gNߓ2X@y2br $oPV$(1l^ss]Q}]xDao@EDHG#3|G )dEd$8D@4O;1R8}؃\ Q}`(^Ɠ',6ȿfK3uH!hHɕFKi2 XG?d$_PDp䓫 (_}t\EW4}""sP;$o%2ws]1&d9B}^'#{u}2l&@?>Ih*BG$ٲ+EE%rB!9;Af0PrB6x?'[9spwգjT ^1{ɵ<_hjW8<`UkY+ak0X]'򈸦/oܝ@3 ) `>˴ 2f}b٣lY]ZQvxR#/b PB \E"8xqlF]5di=J .HKV AaL<4EDCB 0?Cr Jǣ! 1Ëk2)ΌqeJ="V2\,V[d\hxW8X@*MK*ǥ df~7T1aM7ሑM>-?PE+ ~{`CZȸ5K;2Y/7 EV` XgExp  ; Ƽ25y9NN䏼r|.0TmWBl͓!C5OZ)]}6D 𑡺 rD5b#S'Bcoduy4z$BQ 'Ӷvᛢ]tf|Њ_ݍm]M#XɎ %=_yEtG"~ƈOj$N:,é]CenO!FA */PO*"tW.LҙO 41[P-UOO' 趡"O1ѧdP>WYnAE6U'qovT:X'62q6"sJ>8Pʐ'QEb3ݰO`4ͨȆ: 8h$*Fm@Ňq;^uzJ=U)BԟҘ |Oӟg'U+bE"4"5Pİ*6VDJj)bX qKE naĭ1,XjC-bE 314"jE 10/Y+b+bp7(bгtC|݊L,b0TC|Z1X[Ec_(bXPG ͌"TӘ8_.bxbܚ8WPQ,\b-bO?~#b%@SKag%9rW1ʷtnpĚ05&M-b(Eԩ"ct$/`*sMgͅ"+6bJiNZ";Y0}?/bHjD,?[0}?1D8>jɸ66E g۬̓s+ 8bSNK1dV IdTZY%NYCt[Ŋdk]a|?+b)K/8`g41 >18I7c2W >/Q >p6-^19⧋'Ң$|{ŨY}R?>"V.UĠwR_Ȑf"DmAѴu"Av+bPWE"VĊX+bE"VĊX+bEg˵IENDB`nml-0.2.4/examples/railtype/gfx/lc_left.png0000644000061700006170000000737212036626441021415 0ustar abuildabuild00000000000000PNG  IHDRasRGBPLTE4^c >x. McuQYi~s ŋf~}~כ"o$$E6!+ZqS|+ S|׈4KRqvxi?ju1βFl X__T/UbIḁ_m\o}MR73HANF@oET-o$L8E>dEpxD 2aL9\-Im;$dbuK5d['6%n 0}aݼAT>=+*A+d-6p| 'Y,B"=d[>Vgi*\ (9O?=0ȍ͐eVżCn0 m{K5'8UV:A bu]%U֊Sշn|=F,{@\2G:ˑC\zY%O~oEasQ@2# o|{{Kxj1Hf9,$(3 A΀|x\3: ڿrl :Lϸk: D}@7i~,\{V١if[?,7D7H{8Q r&7aw:1u\Kn݆d8'NOj iݷku 5Ƚ?2鹚ou/SDs"/ oIA/bb7iG }1):OsboD =oee$oؽ콽~"m~gk1/ֺoV#o>jo8}rE nBlCI4]>a'I#NA^siV]_C iffB9=7+Vϻ |vCTجDZ!sH{^Z),#z,zҜÐe4Y+a׏1AJ3 ]wD*jx$ߋ#8o9D*qp}YX!;Dw/J{s_!gBhRI>Q݋Ҟ_/{дD~n»p]!t$]/RM\2K !}TXV. +ǭ!{{^މi xzYs$@yTO$ tJ$?29DU~*)YraBd=/[iYJPU>*diVe` z QV|p4kB҇Oj: A@!B!@!B @ @ X?*gGnlgJ>ߝ>Vn\/*QN]:$I'Vn?*QpөWwKV +zU 9ƀT Y~JNZ*L,RaoScԐe%l>.#?bQ\Oc^]eӷJ8deؙ@3r|BԹ5NIt< l>d%Y$V!9TIc'>+6r) 9l[ N^+$M:Q3Z>QĽ{;9@FUBס`7sG,e #Z\τb v'o=oq 7QMu/uԭj?$w 4 b| oxI ?[K @ @ @!BB  DrI&]҄Gdct<DyAr/ ҞDXE!>xS@ŐUYkp?dyɽ8 Z1* ^@2u>gAd>f+=BxkcٛQ!p݋A2^/A ,E" nw9 ׍E Hn'[yϽ9dH$ $ 8);??z^*88v[QU (0(=žS!QsHB"l#KYTr@: @ @ @!BBlA~$4˄pvKy Xb. Qa5գAdAXAV Рjju#aƢǨXb%#)b΃a kmLXcX!7Qt.eq Uu8Mn䬘Uq'G$TU[lcT (OI1T``uu%^c-@R,XL4PWt 4B.sMUi)cڪЙ`jb$~#&a#J){2嗑gŐrRوeFf31EU.X 1IK99{#VR*引"J1RX+(`G>h7)F#;)ƣxTlgT_C}ޥ7bѿ^>}t3`,i 2VAXEXXXXXa+cGX- IJn`i :F6Ю}A)Rqܫ 9MwRz\qVᚾSկV{;ێ%2|rN{j^mn{ l#V)&|r52j':n,R+v4<e]bN9?0Ueq_[N9%s澎JxdpA" mTp2ī. @Xl_}Xo7 b`~ 87 տ?bY_>כ"g$$E6&+^pH oU|kD rHRpw?Ku:noe􁜫cs+UbaL^ŗ`ۅ`Wdt({,.3nXvbA$ ->\&+X % cjv/(Ab@:8>.6M$4Y#A8M(A^fR>us&с2ob=?wp<GG%j !A$EUHf +sV..*e+{p*@ViιS3պBRi2sȥ:~~V?w9.Uձ jS>*Q<쮾Y~WVm#1 YՑ$r Q!!W o30^2v;۝e G?/A<8f3oA ^|HK6=ϹIm[CNsY1(tϸj5bf׹pST_ (~*vPrr=7vGJo. 3(U47a'1i\ݺM(zq5'?`;I~{N5A2ѾTƺ;=dsw1ɔX?Hk*{`iǾco߬vVF޺_qq{~]vA _n[.&q& +$?ĈO F:'";TxX+1A W ,߇Z'lJX O=(!V,VQYobqe4.lP\+EeҖʎ2|k%>D~T>DCWBUHlB!G@O+`B^pC#O+nK,Ȇv+$cdeBCr )VH$CCJd !}țY:;x~ӯMu1|6H,|q)=(N=v< )sɒUH1`B8Cd!eR\( ORڇ(?0+$_r\ +)vP!K+d݇,H6 /daD2H$\\FN*$삨W,Hq\VG #@- @ @!@!BȰϗ#  Y%}lI?Lڛ\(w8H?ϨVIK'/'`3jo"7+ʭb!fQ~I?L[{?-V1HYVp hVI+߾] dV{k}qכoCՊrXlWz, 7K3ɺҭ֬&يrX쥨MjxtV%[zY3!  _!z*DfK튟r}ONd?rX!Êr8\ߓQ!7R5qP! >|U+$>FTHyFW\[H__FQ.Rynz@4嫊](ר([C1a m&;pܑDӃ\G2/2E|UA!S'Tn w=ɺK_ Ӗ25ɝH9ىQUtÀ]  @ @ @!BB  @h&[O$mۇ9A7 GOANށl!^L/q!hR#M7YD:Q#W '@NE2 x؇ȏJ"GsQ1{8&^>* II<QJC3y8*19y "ZpeLK>H"3$8CdnI9n@yשid9 'AL~`ށ$& -A 0qw Ge@G !Iq\VAT+q W#@- @ @!@!BI_@Ak@setLrT=b$~<8Yt$AC()޾r~nf]/z[QV%IG} d GJxCUR vAnd}-9(: O{OZY=,Ș@m#@CZVzYV!IHl9/ֲjVG.yWuBdD=+hO:L_!a$'%mDnBUE$O+XaŠ}'iܾN.KkF:wbV!Y0=i?~iL*3qsȈn w=X ɝHyDyl@*dad.@!B!B @ @ @!BB  @ @!% @ @ @!B!B @ @ @!BB  @ @!@!B @ @ @!BB  @ @!@!B ?rA-IENDB`nml-0.2.4/examples/railtype/lang/0000755000061700006170000000000012036626604017424 5ustar abuildabuild00000000000000nml-0.2.4/examples/railtype/lang/english.lng0000644000061700006170000000073412036626441021562 0ustar abuildabuild00000000000000##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Railtype STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Railtype{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}Irwe, {BLACK}coding by {SILVER}planetmaker.{}{BLACK}This NewGRF defines a graphical replacement for normal and electric rails nml-0.2.4/examples/railtype/example_railtype.nml0000644000061700006170000002676312036626441022574 0ustar abuildabuild00000000000000/* * This file is aimed to provide an example on how to code a railtype in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * The NewGRF implements a graphical replacement for the normal and electric * rails. Since almost all sprites (except caternary) are supplied, you can * use this grf to see in detail what sprites are needed and in what order. * * Essentially this is a cut-down version of the Swedish Rails grf, drawn by * Irwe and coded by planetmaker. Support for parameters, time-dependent * graphics and snow support has been removed to keep this example within * reasonable size. Due to the large quantity of sprites required for a * railtype grf, the number of lines of code is still relatively high. * * All real sprites have been templated, even if the template is used only * once. This allows adding e.g. snowed graphics fairly easily. * * Apart from this file, you will also need the following * - Graphics, found in in the gfx folder * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /********************************************** * Header, containing some general stuff: **********************************************/ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the third real grf defined as part of * NML, therefore the last character is set to 2. Successive grfs will * have 3, 4, etc. there, to make sure each example grf has a unique GRFID. */ grfid : "NML\02"; name : string(STR_GRF_NAME); desc : string(STR_GRF_DESCRIPTION); version : 0; // must be numeric min_compatible_version : 0; } /* Check for NuTracks and disable, if we're not active _after_ NuTracks */ if (!grf_order_behind("DJT\01")) { error(FATAL, MUST_LOAD_AFTER, "NuTracks"); } /* Default ground tile template (re-use as needed) */ template ground_tile(x, y) { [x, y, 64, 31, -31, 0, TILE] } /********************************************** * Track underlays (tracks + ballast): **********************************************/ /* Underlays (single track bits with ballast)\ * Used for bridge surfaces also, therefore the template is split */ template tmpl_underlay_straight() { ground_tile(75, 0) ground_tile( 0, 0) } template tmpl_underlay_slope() { [ 75, 40, 64,39, -31, -8] [150, 40, 64,23, -31, 0] [225, 40, 64,23, -31, 0] [300, 40, 64,39, -30, -9] } template tmpl_underlay_diagonal() { ground_tile(150, 0) ground_tile(225, 0) ground_tile( 0, 40) ground_tile(300, 0) } template tmpl_underlay_railtypes() { tmpl_underlay_straight() tmpl_underlay_diagonal() tmpl_underlay_slope() /* X-crossing */ ground_tile(0, 120) /* underlay for crossings w/o tracks */ ground_tile( 0, 80) ground_tile(225, 80) ground_tile(150, 80) ground_tile( 75, 80) ground_tile(300, 80) } /* Spriteset containing all underlays */ spriteset(track_underlays, "gfx/rails_overlays.png") { tmpl_underlay_railtypes() } /********************************************** * Track overlays (tracks without ballast): **********************************************/ /* Template for overlays; 2x straight track, 4x diagonal track, 4x slope */ template tmpl_overlay_railtypes() { [ 0,155, 40,21, -19, 5] [ 50,155, 40,21, -19, 5] [100,155, 40, 7, -19, 4] [150,155, 40, 7, -21, 20] [200,155, 12,19, 11, 6] [250,155, 12,19, -21, 6] [ 0,195, 64,39, -33, -8] [ 75,195, 64,23, -31, 0] [150,195, 64,23, -31, 0] [225,195, 64,39, -32, -9] } /* Spriteset for overlays */ spriteset(track_overlays, "gfx/rails_overlays.png") { tmpl_overlay_railtypes() } /********************************************** * Level crossings: **********************************************/ /* Level crossings require differing sprites depending * on the open/closed state and on the driving side */ /* Template for the track overlays (x/y) */ template tmpl_rails_crossing(x,y) { [x, y, 44, 23, -21, 4] [x+50, y, 44, 23, -21, 4] } template tmpl_level_crossing_railtypes_open(y) { tmpl_rails_crossing(5, 5) [ 0, y, 5,12, -3, -8] [ 50, y, 8,21, -5, -14] [100, y, 6,23, -7, -20] [150, y, 5,12, -5, -8] [200, y, 7,21, 3, -15] [250, y, 5,12, -1, -8] [300, y, 5,12, -3, -10] [350, y, 8,22, -3, -19] } template tmpl_level_crossing_railtypes_closed(y) { tmpl_rails_crossing(5, 5) [ 0, y, 5, 12, -3, -8] [ 50, y, 19, 19, -4, -6] [100, y, 23, 17, -24, -9] [150, y, 5, 12, -5, -8] [200, y, 25, 14, 3, -9] [250, y, 5, 12, -1, -8] [300, y, 5, 12, -3, -10] [350, y, 19, 14, -15, -11] } template tmpl_level_crossing_railtypes_left_open(y) { tmpl_rails_crossing(5, 5) [ 0, y, 7, 21, 0, -14] [ 50, y, 5, 12, -2, -6] [100, y, 5, 12, -3, -9] [150, y, 7, 21, -7, -15] [200, y, 5, 12, 4, -7] [250, y, 7, 22, 0, -17] [300, y, 6, 21, -2, -19] [350, y, 5, 12, -3, -9] } template tmpl_level_crossing_railtypes_left_closed(y) { tmpl_rails_crossing(5, 5) [ 0, y, 21, 19, -14, -6] [ 50, y, 5, 12, -2, -6] [100, y, 5, 12, -3, -9] [150, y, 23, 15, -23, -9] [200, y, 5, 12, 4, -7] [250, y, 23, 17, 0, -7] [300, y, 21, 13, -2, -11] [350, y, 5, 12, -3, -9] } // right hand traffic: spriteset(lc_right_closed, "gfx/lc_right.png") { tmpl_level_crossing_railtypes_closed(100) } spriteset(lc_right_open, "gfx/lc_right.png") { tmpl_level_crossing_railtypes_open(50) } // left hand traffic: spriteset(lc_left_closed, "gfx/lc_left.png") { tmpl_level_crossing_railtypes_left_closed(100) } spriteset(lc_left_open, "gfx/lc_left.png") { tmpl_level_crossing_railtypes_left_open(50) } switch(FEAT_RAILTYPES, SELF, right_level_crossing_state_switch, level_crossing_status) { LEVEL_CROSSING_CLOSED: lc_right_closed; lc_right_open; } switch(FEAT_RAILTYPES, SELF, left_level_crossing_state_switch, level_crossing_status) { LEVEL_CROSSING_CLOSED: lc_left_closed; lc_left_open; } switch(FEAT_RAILTYPES, SELF, level_crossing_switch, traffic_side) { TRAFFIC_SIDE_LEFT: left_level_crossing_state_switch; right_level_crossing_state_switch; } /********************************************** * Tracks in tunnels: **********************************************/ /* Template for tunnel track overlays */ template tmpl_tunnel_tracks() { ground_tile(75, 0) ground_tile( 0, 0) ground_tile(75, 50) ground_tile( 0, 50) } spriteset(tunnel_overlays, "gfx/tunnel_track.png") { tmpl_tunnel_tracks() } /********************************************** * Depots: **********************************************/ /* Template for depot sprites */ template tmpl_depot() { [200, 10, 16, 8, 17, 7] [118, 8, 64, 47, -9, -31] [ 0, 10, 16, 8, -31, 7] [ 37, 8, 64, 47, -53, -31] [ 37, 63, 64, 47, -53, -31] [118, 63, 64, 47, -9, -31] } /* Depots have differing sprites for normal and e-rail */ spriteset(depot_normal_rail, "gfx/depot_normal.png") { tmpl_depot() } spriteset(depot_electric_rail, "gfx/depot_electric.png") { tmpl_depot() } /********************************************** * Bridge surfaces: **********************************************/ /* Bridge surface, uses the same sprites as track underlays, but in a different order */ template tmpl_bridges_underlay() { tmpl_underlay_straight() tmpl_underlay_slope() tmpl_underlay_diagonal() } /* Spriteset for bridge surfaces */ spriteset(bridge_underlay, "gfx/rails_overlays.png") { tmpl_bridges_underlay() } /********************************************** * Fences: **********************************************/ /* Template for fences, parametrized to allow multiple sets of fences (unused) */ template tmpl_fences(y) { [ 0, y, 32,20, -30, -4] [ 48, y, 32,20, 0, -3] [ 96, y, 2,30, 0,-17] [112, y, 64, 5, -30, -4] [192, y, 32,12, -30, -4] [240, y, 32,12, 2, -3] [288, y, 32,28, -31,-12] [350, y, 32,28, 1,-10] } /* Spriteset for (company-coloured) fences */ spriteset(fencesCC, "gfx/fences.png") { tmpl_fences(0) } /********************************************** * GUI sprites: **********************************************/ /* Template for a single icon sprite */ template tmpl_gui_icon(x, y) { [x, y, 20, 20, 0, 0] } /* Template for a single cursor sprite */ template tmpl_gui_cursor(x, y) { [x, y, 32, 32, 0, 0] } /* Template for all the GUI sprites (8 icons + 8 cursors) */ template tmpl_gui() { tmpl_gui_icon( 0, 0) tmpl_gui_icon( 25, 0) tmpl_gui_icon( 50, 0) tmpl_gui_icon( 75, 0) tmpl_gui_icon(100, 0) tmpl_gui_icon(125, 0) tmpl_gui_icon(150, 0) tmpl_gui_icon(175, 0) tmpl_gui_cursor(200, 0) tmpl_gui_cursor(250, 0) tmpl_gui_cursor(300, 0) tmpl_gui_cursor(350, 0) tmpl_gui_cursor(400, 0) tmpl_gui_cursor(450, 0) tmpl_gui_cursor(500, 0) tmpl_gui_cursor(550, 0) } /* Spritesets for the normal and electric GUI */ spriteset(gui_normal, "gfx/gui_rail.png") { tmpl_gui() } spriteset(gui_electric, "gfx/gui_erail.png") { tmpl_gui() } /********************************************** * Railtype definitions: **********************************************/ /* Define the normal rails */ item(FEAT_RAILTYPES, rail) { /* Set only the most essential properties, * Lots of compatible railtypes are defined to allow compatibility with * various other sets out there */ property { label: "RAIL"; // Let this railtype replace the default normal rails compatible_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; powered_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; } /* Associate graphics with this railtype */ graphics { track_overlay: track_overlays; underlay: track_underlays; level_crossings: level_crossing_switch; tunnels: tunnel_overlays; depots: depot_normal_rail; bridge_surfaces: bridge_underlay; fences: fencesCC; gui: gui_normal; /* Caternary is not not implemented here, use the default */ } } /* Define the electric rails */ item(FEAT_RAILTYPES, elrail) { /* Set only the most essential properties, * Lots of compatible railtypes are defined to allow compatibility with * various other sets out there */ property { label: "ELRL"; // Let this railtype replace the default electric rails compatible_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; powered_railtype_list: ["ELRL", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNE", "DBHE"]; } /* Associate graphics with this railtype */ graphics { track_overlay: track_overlays; underlay: track_underlays; level_crossings: level_crossing_switch; tunnels: tunnel_overlays; depots: depot_electric_rail; bridge_surfaces: bridge_underlay; fences: fencesCC; gui: gui_electric; /* Caternary is not not implemented here, use the default */ } } nml-0.2.4/nml.egg-info/0000755000061700006170000000000012036626604015314 5ustar abuildabuild00000000000000nml-0.2.4/nml.egg-info/PKG-INFO0000644000061700006170000000135312036626604016413 0ustar abuildabuild00000000000000Metadata-Version: 1.0 Name: nml Version: 0.2.4 Summary: A tool to compile nml files to grf or nfo files Home-page: http://dev.openttdcoop.org/projects/nml Author: NML Development Team Author-email: nml-team@openttdcoop.org License: GPLv2 Description: A tool to compile nml files to grf and / or nfo files.NML is a meta-language that aims to be a lot simpler to learn and use than nfo. Platform: UNKNOWN Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Compilers nml-0.2.4/nml.egg-info/dependency_links.txt0000644000061700006170000000000112036626604021362 0ustar abuildabuild00000000000000 nml-0.2.4/nml.egg-info/entry_points.txt0000644000061700006170000000007112036626604020610 0ustar abuildabuild00000000000000 [console_scripts] nmlc = nml.main:run nml-0.2.4/nml.egg-info/top_level.txt0000644000061700006170000000000412036626604020040 0ustar abuildabuild00000000000000nml nml-0.2.4/nml.egg-info/SOURCES.txt0000644000061700006170000001507112036626604017204 0ustar abuildabuild00000000000000MANIFEST.in Makefile bootstrap.py buildout.cfg nmlc setup.py docs/changelog.txt docs/index.html docs/license.txt docs/nmlc.1 docs/readme.txt docs/zpl-2.1.txt examples/object/cc_grid.png examples/object/example_object.nml examples/object/lang/english.lng examples/railtype/example_railtype.nml examples/railtype/gfx/depot_electric.png examples/railtype/gfx/depot_normal.png examples/railtype/gfx/fences.png examples/railtype/gfx/gui_erail.png examples/railtype/gfx/gui_rail.png examples/railtype/gfx/lc_left.png examples/railtype/gfx/lc_right.png examples/railtype/gfx/rails_overlays.png examples/railtype/gfx/tunnel_track.png examples/railtype/lang/english.lng examples/road_vehicle/example_road_vehicle.nml examples/road_vehicle/gfx/flatbed_truck_1_copper.png examples/road_vehicle/gfx/flatbed_truck_1_goods.png examples/road_vehicle/gfx/flatbed_truck_1_paper.png examples/road_vehicle/gfx/flatbed_truck_1_steel.png examples/road_vehicle/gfx/flatbed_truck_1_wood.png examples/road_vehicle/lang/english.lng examples/train/example_train.nml examples/train/icm.png examples/train/lang/dutch.lng examples/train/lang/english.lng nml/__init__.py nml/__version__.py nml/free_number_list.py nml/generic.py nml/global_constants.py nml/grfstrings.py nml/lz77.py nml/main.py nml/nmlop.py nml/output_base.py nml/output_dep.py nml/output_grf.py nml/output_nfo.py nml/output_nml.py nml/palette.py nml/parser.py nml/tokens.py nml/unit.py nml/version_info.py nml.egg-info/PKG-INFO nml.egg-info/SOURCES.txt nml.egg-info/dependency_links.txt nml.egg-info/entry_points.txt nml.egg-info/top_level.txt nml/actions/__init__.py nml/actions/action0.py nml/actions/action0properties.py nml/actions/action1.py nml/actions/action10.py nml/actions/action11.py nml/actions/action12.py nml/actions/action14.py nml/actions/action2.py nml/actions/action2layout.py nml/actions/action2production.py nml/actions/action2random.py nml/actions/action2real.py nml/actions/action2var.py nml/actions/action2var_variables.py nml/actions/action3.py nml/actions/action3_callbacks.py nml/actions/action4.py nml/actions/action5.py nml/actions/action6.py nml/actions/action7.py nml/actions/action8.py nml/actions/actionA.py nml/actions/actionB.py nml/actions/actionD.py nml/actions/actionE.py nml/actions/actionF.py nml/actions/base_action.py nml/actions/real_sprite.py nml/actions/sprite_count.py nml/ast/__init__.py nml/ast/alt_sprites.py nml/ast/assignment.py nml/ast/base_sprites.py nml/ast/base_statement.py nml/ast/basecost.py nml/ast/cargotable.py nml/ast/conditional.py nml/ast/deactivate.py nml/ast/disable_item.py nml/ast/error.py nml/ast/font.py nml/ast/general.py nml/ast/grf.py nml/ast/item.py nml/ast/loop.py nml/ast/override.py nml/ast/produce.py nml/ast/railtypetable.py nml/ast/replace.py nml/ast/skipall.py nml/ast/snowline.py nml/ast/sort_vehicles.py nml/ast/spriteblock.py nml/ast/switch.py nml/ast/tilelayout.py nml/ast/townnames.py nml/expression/__init__.py nml/expression/array.py nml/expression/base_expression.py nml/expression/bin_not.py nml/expression/binop.py nml/expression/bitmask.py nml/expression/boolean.py nml/expression/functioncall.py nml/expression/functionptr.py nml/expression/identifier.py nml/expression/parameter.py nml/expression/patch_variable.py nml/expression/special_parameter.py nml/expression/spritegroup_ref.py nml/expression/storage_op.py nml/expression/string.py nml/expression/string_literal.py nml/expression/ternaryop.py nml/expression/variable.py regression/001_action8.nml regression/002_sounds.nml regression/003_assignment.nml regression/004_deactivate.nml regression/005_error.nml regression/006_vehicle.nml regression/007_townnames.nml regression/008_railtypes.nml regression/009_replaceTTDsprite.nml regression/010_liveryoverride.nml regression/011_snowline.nml regression/012_basecost.nml regression/013_train_callback.nml regression/014_read_special_param.nml regression/015_basic_object.nml regression/016_basic_airporttiles.nml regression/017_articulated_tram.nml regression/018_airport_tile.nml regression/019_switch.nml regression/020_recolour.nml regression/021_grf_parameter.nml regression/022_disable_item.nml regression/023_engine_override.nml regression/024_conditional.nml regression/025_loop.nml regression/026_asl.nml regression/027_airport_layout.nml regression/Makefile regression/arctic_railwagons.pcx regression/beef.wav regression/opengfx_generic_trams1.pcx regression/opengfx_trains_start.pcx regression/temperate_railwagons.png regression/tram_foster_express.png regression/expected/001_action8.grf regression/expected/001_action8.nfo regression/expected/002_sounds.grf regression/expected/002_sounds.nfo regression/expected/003_assignment.grf regression/expected/003_assignment.nfo regression/expected/004_deactivate.grf regression/expected/004_deactivate.nfo regression/expected/005_error.grf regression/expected/005_error.nfo regression/expected/006_vehicle.grf regression/expected/006_vehicle.nfo regression/expected/007_townnames.grf regression/expected/007_townnames.nfo regression/expected/008_railtypes.grf regression/expected/008_railtypes.nfo regression/expected/009_replaceTTDsprite.grf regression/expected/009_replaceTTDsprite.nfo regression/expected/010_liveryoverride.grf regression/expected/010_liveryoverride.nfo regression/expected/011_snowline.grf regression/expected/011_snowline.nfo regression/expected/012_basecost.grf regression/expected/012_basecost.nfo regression/expected/013_train_callback.grf regression/expected/013_train_callback.nfo regression/expected/014_read_special_param.grf regression/expected/014_read_special_param.nfo regression/expected/015_basic_object.grf regression/expected/015_basic_object.nfo regression/expected/016_basic_airporttiles.grf regression/expected/016_basic_airporttiles.nfo regression/expected/017_articulated_tram.grf regression/expected/017_articulated_tram.nfo regression/expected/018_airport_tile.grf regression/expected/018_airport_tile.nfo regression/expected/019_switch.grf regression/expected/019_switch.nfo regression/expected/020_recolour.grf regression/expected/020_recolour.nfo regression/expected/021_grf_parameter.grf regression/expected/021_grf_parameter.nfo regression/expected/022_disable_item.grf regression/expected/022_disable_item.nfo regression/expected/023_engine_override.grf regression/expected/023_engine_override.nfo regression/expected/024_conditional.grf regression/expected/024_conditional.nfo regression/expected/025_loop.grf regression/expected/025_loop.nfo regression/expected/026_asl.grf regression/expected/026_asl.nfo regression/expected/027_airport_layout.grf regression/expected/027_airport_layout.nfo regression/lang/dutch.lng regression/lang/english.lng regression/lang/us.lngnml-0.2.4/nmlc0000755000061700006170000000013012036626441013704 0ustar abuildabuild00000000000000#! /usr/bin/env python from nml import main if __name__ == "__main__": main.run() nml-0.2.4/docs/0000755000061700006170000000000012036626604013764 5ustar abuildabuild00000000000000nml-0.2.4/docs/license.txt0000644000061700006170000004310412036626441016150 0ustar abuildabuild00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. nml-0.2.4/docs/changelog.txt0000644000061700006170000000675012036626442016464 0ustar abuildabuild000000000000000.2.4 (2012-10-14) ------------------------------------------------------------------------ - Feature: Report NML line information as well as pixel position for pure white pixels. Also, report number of pixels in the sprite, instead of the whole image. (issue #4029) - Feature: 'signals' callback for railtypes. - Feature: Allow the 'nfo' unit to be used with non-constant values. (issue #3828) - Feature: 'build_prod_change' callback for industries to set industry production level on construction. - Feature: Constant CB_RESULT_REFIT_COST_MASK - Feature: Vehicle misc_flag VEHTYPE_FLAG_NO_BREAKDOWN_SMOKE. - Feature: 'current_max_speed' variable for vehicles. (issue #3979) - Feature: 'vehicle_is_in_depot' variable for aircraft. - Feature: 'range' property and callback for aircraft. - Feature: 'production_rate_1/2' variables for industries. - Feature: 'town_zone' variable for railtypes. - Feature: 'other_veh_(curv_info|is_hidden|x_offset|y_offset|z_offset)' variables for vehicles. - Fix: Provide a proper error message when running out of action2 IDs - Fix: A '{' at the end of a string could cause a crash - Fix: Backslash-escapes in strings weren't properly validated. Also remove useless \n escape. (issue #3636) - Fix: Provide a proper error message if a substring is missing, instead of an assertion error. (issue #3932) - Fix: 'refit_cost' callback may also be called from the purchase menu. - Fix: Allow using constants > 255 as variable parameter. (issue #4086) - Fix: Sprite layout register code contained an unsorted iteration over dictionary keys, resulting in possible regression failures. 0.2.3 (2012-02-19) ------------------------------------------------------------------------ - Feature: Action5 for tunnel portals - Fix: Properly catch out-of-bounds image reads (issue #3666) - Fix: Character code 0xA0 (NBSP) is used for an up arrow in TTD, so don't write it as ascii. Force unicode instead (issue #3643) 0.2.2 (2012-01-29) ------------------------------------------------------------------------ - Feature: support for (optional) url-information in the grf-block - Feature: names for newly defined cargo classes: CC_POWDERIZED and CC_NEO_BULK - Feature: clean target to Makefile in main dir and let make clean remove regression/parsetab.py - Fix: don't crash when a line in custom_tags.txt is missing a colon delimiter - Fix: each action4 is limited to 255 strings. Write multiple actions when we have more than that - Fix: groff warning about manpage - Fix: include buildout.cfg in src distribution and prune regression/nml_output/ (issue #3490) - Fix: Add NML output for replacenew-block (issue #3450) - Fix: include nmlc script so regression runs out-of-the-box for source package - Fix: several files from examples/ were missing in source distributions - Fix: 'make install' was broken - Doc: add notice about ZPL in readme.txt 0.2.1 (2011-12-18) ------------------------------------------------------------------------ - Feature: CB_RESULT_NO_MORE_ARTICULATED_PARTS, CB_RESULT_REVERSED_VEHICLE and CB_RESULT_NO_TEXT as constants to make porting projects to NML 0.3 easier. - Fix: Internal error when the ID in a replace-block was not a compile-time constant - Fix: Crash when referencing a SpriteSetCollection in a graphics-block that was inside an if-block - Fix: Text files in docs/ were not included in source package - Doc: Add GPL v2 header to all .py files 0.2.0 (2011-11-20) ------------------------------------------------------------------------ No changelog available for 0.2.0 and earlier. nml-0.2.4/docs/zpl-2.1.txt0000644000061700006170000000404012036626441015625 0ustar abuildabuild00000000000000ZPL 2.1 Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nml-0.2.4/docs/index.html0000644000061700006170000000123412036626441015760 0ustar abuildabuild00000000000000 NML documentation

The NML documentation has been moved to the following location: http://newgrf-specs.tt-wiki.net/wiki/NML:Main.

You will be redirected in a few seconds, click the link if this doesn't happen (most likely because meta refresh is disabled in your browser).

nml-0.2.4/docs/nmlc.10000644000061700006170000000363512036626442015006 0ustar abuildabuild00000000000000.Dd June 18, 2011 .Dt NMLC 1 .Sh NAME .Nm NMLC .Nd A compiler from NML code to NFO and/or GRF files. .Sh SYNOPSIS .Nm nmlc .Op options .Op file .Sh OPTIONS .Bl -tag .It Fl c Crop extraneous transparent blue from real sprites. .It Fl u Save real sprites uncompressed to GRF files. This saves a lot of time during encoding but it's not recommended when creating a file for distribution since it makes the output file substantially bigger. .It Fl -grf Ns = Ns Ar file Write output in GRF format to . .It Fl -nfo Ns = Ns Ar file Write output in NFO format to . .It Fl -nml Ns = Ns Ar file Write output in NML format to . .It Fl -output Ns = Ns Ar file | Fl o Ar file Write output to . The output type is detected from the extension of the filename. It must be one of nfo, nml or grf. .It Fl -debug | Fl d Print a dump of the AST to stdout. .It Fl -help | Fl h Print usage information. .It Fl -stack | Fl s Dump stack when an error occurs .It Fl -custom-tags Ns = Ns Ar file | Fl t Ar file Load custom tags from [default: custom_tags.txt] .It Fl -lang-dir Ns = Ns Ar dir | Fl l Ar dir Load language files from directory [default: lang] .It Fl -default-lang Ns = Ns Ar file The default language is stored in [default: english.lng] .It Fl -sprites-dir Ns = Ns Ar dir | Fl a Ar dir Store 32bpp sprites in directory [default: sprites] .It Fl -start-sprite Ns = Ns Ar file Set the first sprite number to write (do not use except when you output nfo that you want to include in other files). .It Fl -palette Ns = Ns Ar palette | Fl p Ar palette Force nml to use the palette [default: ANY]. Valid values are 'DOS', 'WIN', 'ANY'. .El .Sh SEE ALSO The language reference at .Pa http://hg.openttdcoop.org/nml/raw-file/tip/docs/index.html .Sh AUTHOR NML was written by Albert Hofkamp, Jasper Reichardt, Ingo von Borstel, José Soler and Thijs Marinussen. .Pp This manual page was written by Thijs Marinussen. nml-0.2.4/docs/readme.txt0000644000061700006170000001057012036626442015765 0ustar abuildabuild00000000000000NML readme Last updated: 2012-10-14 Release version: 0.2.4 ------------------------------------------------------------------------ Table of Contents: ------------------ 1) About 2) Contact 3) Dependencies 3.1) Required dependencies 3.2) Optional dependencies 4) Installation 5) Usage 6) Known issues 7) Credits 1) About: -- ------ NML is a a python-based compiler, capable of compiling NML files (along with their associated language, sound and graphic files) into grf and / or nfo files. The documentation about the language can be found on http://newgrf-specs.tt-wiki.net/wiki/NML:Main NML is licensed under the GNU General Public License version 2, or at your option, any later version. For more information, see 'license.txt' (GPL version 2), or later versions at . The file bootstrap.py is licensed under Zope Public License (ZPL) Version 2.1. See zpl-2.1.txt for more information. 2) Contact: -- -------- Contact can be made via the issue tracker / source repository at http://dev.openttdcoop.org/projects/nml or via IRC on the #openttdcoop.devzone channel on OFTC. 3) Dependencies: -- ------------- 3.1) Required dependencies: ---- ---------------------- NML requires the following 3rd party packages to run: - python Minimal version is 2.5. Python 3 is not yet supported. - python image library downloadable from http://www.pythonware.com/products/pil/ - ply downloadable from http://www.dabeaz.com/ply/ 3.2) Optional dependencies: ---- ---------------------- To install NML you'll need these 3rd party packages: - buildout Only necesary if you want to use the installer. You can run NML without installation or manually install it. 4) Installation: -- ------------- NML uses buildout for packaging / installation. To install NML run: python setup.py install If you want to install the package manually copy 'nmlc' to any directory in your path and the directory 'nml' to any directory in your python path. 5) Usage: -- ------ Usage: nmlc [options] Where is the nml file to parse Options: --version show program's version number and exit -h, --help show this help message and exit -d, --debug write the AST to stdout -s, --stack Dump stack when an error occurs --grf= write the resulting grf to --nfo= write nfo output to -M output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or --grf) --MF= When used with -M, specifies a file to write the dependencies to --MT= target of the rule emitted by dependency generation (requires -M) -c crop extraneous transparent blue from real sprites -u save uncompressed data in the grf file --nml= write optimized nml to -o , --output= write output(nfo/grf) to -t , --custom-tags= Load custom tags from [default: custom_tags.txt] -l , --lang-dir= Load language files from directory [default: lang] -a , --sprites-dir= Store 32bpp sprites in directory [default: sprites] --default-lang= The default language is stored in [default: english.lng] --start-sprite= Set the first sprite number to write (do not use except when you output nfo that you want to include in other files) -p , --palette= Force nml to use the palette [default: ANY]. Valid values are 'DOS', 'WIN', 'ANY' 6) Known issues: -- ------------- See the issue tracker at https://dev.openttdcoop.org/projects/nml/issues 7) Credits: -- -------- Active developers (in alphabetical order): Jasper Reichardt (Hirundo) Ingo von Borstel (planetmaker) José Soler (Terkhen) Thijs Marinussen (Yexo) Inactive developers: Albert Hofkamp (Alberth) Special thanks to: Richard Barrell For writing the buildout script needed to install NML. nml-0.2.4/bootstrap.py0000644000061700006170000000733712036626441015434 0ustar abuildabuild00000000000000############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id$ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) nml-0.2.4/setup.py0000755000061700006170000000310612036626442014551 0ustar abuildabuild00000000000000#! /usr/bin/env python import sys, os, string, subprocess from setuptools import setup version = sys.version_info if version[0] < 2 or (version[0] == 2 and version[1] < 5): sys.exit('ERROR: Sorry, python 2.5 ... < 3.0 is required for this application.') if version[0] >= 3: sys.exit('WARNING: Sorry, python 3.0 or later is not yet supported. Some parts may not work.') # For the purpose of the packet information we only use the numeric code from nml import version_info version = version_info.get_and_write_version() setup(name='nml', version=version, description='A tool to compile nml files to grf or nfo files', long_description = 'A tool to compile nml files to grf and / or nfo files.' \ 'NML is a meta-language that aims to be a lot simpler to learn and use than nfo.', license='GPLv2', classifiers = ['Development Status :: 2 - Pre-Alpha', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Software Development :: Compilers', ], packages=['nml', 'nml.actions', 'nml.ast', 'nml.expression'], url='http://dev.openttdcoop.org/projects/nml', author='NML Development Team', author_email='nml-team@openttdcoop.org', entry_points=""" [console_scripts] nmlc = nml.main:run """ ) nml-0.2.4/setup.cfg0000644000061700006170000000007312036626604014655 0ustar abuildabuild00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0