/* $Filename: WormWars/Source/engine.c * $VER: WormWars 7.0 * * © Copyright 1993-2002 James R. Jacobs. Freely distributable. */ #include #include #include #include /* EXIT_SUCCESS, EXIT_FAILURE */ #include #define ASSERT #include "stdafx.h" #include "diff.h" #include "same.h" #define OTTER_UP 0 #define OTTER_DOWN 1 #define OTTER_LEFT 2 #define OTTER_RIGHT 3 #define ARROWX (FIELDX + 1) #define BONUSSPEEDUP 8 // 8 times more common on bonus levels #define NOSE 2 // if (NOSE > PROTECTORS) nose disabled #define WORMQUEUELIMIT 15 #define DOGQUEUELIMIT 120 #define TIMELIMIT 599 #define SECONDSPERLEVEL 120 // assert (SECONDSPERLEVEL <= TIMELIMIT); #define WEIGHT 5 // dog dormancy #define DORMANT 0 #define AWAKENING 1 #define CHASING 10 // population limits #define CREATURES 50 #define MAGNETS 20 #define PROTECTORS 2 // <=4! #define OCTOPI 12 // this limit applies only to predefined octopi #define FREQ_BIRD 140 #define FREQ_CLOUD 55 #define FREQ_CLOUDFIRE 30 #define FREQ_DOG 55 #define FREQ_DRIP 15 #define FREQ_FISH 105 #define FREQ_GOAT 30 #define FREQ_GOATFIRE 10 #define FREQ_GOATMOVE 5 #define FREQ_OCTOPUS 90 #define FREQ_OCTOPUSFIRE 40 #define FREQ_OCTOPUSSPIN 3 #define FREQ_ORB 35 #define FREQ_PENGUIN 40 #define FREQ_SLIME 80 #define FREQ_SLIMEGROW 75 #define FREQ_TELEPORT 210 #define FREQ_TIMEBOMB 210 #define SPEED_BIRD 10 #define SPEED_CLOUD 9 #define SPEED_DOG 3 #define SPEED_DRIP 4 #define SPEED_FISH 12 #define SPEED_FRAGMENT 3 #define SPEED_GOAT 16 #define SPEED_MAGNET 9 #define SPEED_MISSILE 6 #define SPEED_OCTOPUS 16 #define SPEED_ORB 6 #define SPEED_OTTER 19 #define SPEED_PENGUIN 11 #define SPEED_TIMEBOMB 60 #define SPEED_WHIRLWIND 1 #define HARDNESS_BIRD 50 #define HARDNESS_CLOUD 50 #define HARDNESS_DOG 50 #define HARDNESS_DRIP 50 #define HARDNESS_FISH 50 #define HARDNESS_FRAGMENT 50 #define HARDNESS_GOAT 50 #define HARDNESS_MISSILE 50 #define HARDNESS_OCTOPUS 50 #define HARDNESS_OTTER 95 #define HARDNESS_ORB 50 #define HARDNESS_PENGUIN 10 #define HARDNESS_TIMEBOMB 80 #define HARDNESS_WHIRLWIND 90 #define DISTANCE_FAST 5 #define DISTANCE_BIRD 3 #define DISTANCE_NORMAL 4 #define DISTANCE_NOSE 4 #define DISTANCE_SLOW 3 #define DISTANCE_VERYSLOW 2 #define POINTS_EMPTY 1 #define POINTS_DYNAMITE 5 #define POINTS_TURNSILVER 5 #define POINTS_ENCLOSURE 10 #define POINTS_SILVER 10 #define POINTS_TURNGOLD 10 #define POINTS_GOLD 20 #define POINTS_LETTER 100 #define POINTS_GRAVE 100 #define ADD_BOMB 5 // in squares radius #define ADD_CLOCK 10 // in seconds #define ADD_CUTTER 10 // in VERYSLOWs #define ADD_GLOW 20 // in VERYSLOWs #define ADD_ICE 10 // in VERYSLOWs #define ADD_ARMOUR 25 // in VERYSLOWs #define ADD_TREASURE 10 // in seconds #define RAND_BOMB 25 #define RAND_CLOCK 20 #define RAND_CUTTER 20 #define RAND_GLOW 50 #define RAND_ICE 5 #define RAND_ARMOUR 25 #define RAND_TREASURE 10 MODULE void changefield(void); MODULE void death(void); MODULE void fastloop(void); MODULE void killall(void); MODULE void magnetloop(void); MODULE void newhiscores(void); MODULE void slowloop(void); MODULE void ReadGameports(void); MODULE void verynewlevel(void); MODULE void bangdynamite(SBYTE x, SBYTE y, SBYTE player); MODULE void bombblast(SBYTE triggerer, SBYTE player, SBYTE centrex, SBYTE centrey); MODULE void bothcol(SBYTE player, SBYTE x, SBYTE y); MODULE void bouncegoatoctopus(UBYTE which, SBYTE x, SBYTE y); MODULE void __inline change(SBYTE x, SBYTE y, UBYTE image); MODULE void checkrectangle(SBYTE direction, SBYTE player, SBYTE horizontalsize, SBYTE verticalsize); MODULE void cloudbullet(UBYTE which, SBYTE x, SBYTE y, SBYTE deltay); MODULE void creatureloop(SBYTE which); MODULE void dogqueue(SBYTE which, SBYTE deltax, SBYTE deltay); MODULE void drawcause(SBYTE player, SBYTE state); MODULE void getnumber(SBYTE player); MODULE void newlevel(UBYTE player); MODULE void orbsplit(SBYTE which); MODULE void protcol(SBYTE player, SBYTE x, SBYTE y, SBYTE thisprot); MODULE void putnumber(void); MODULE void ramming(SBYTE player); MODULE void reflect(UBYTE which); MODULE void turnworm(SBYTE player, SBYTE deltax, SBYTE deltay); MODULE void updatearrow(SBYTE arrowy); MODULE void wormbullet(SBYTE player); MODULE void wormloop(SBYTE player); MODULE void wormcol(SBYTE player, SBYTE x, SBYTE y); MODULE void wormworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2); MODULE void protworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2); MODULE void protprot(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2); MODULE void octopusfire(UBYTE which); MODULE void squareblast(SBYTE type, SBYTE player, UBYTE c, SBYTE x, SBYTE y, ABOOL cutter); MODULE void drawmissile(SBYTE x, SBYTE y, UBYTE which); MODULE SWORD atleast(SWORD value, SWORD minimum); MODULE ABOOL blocked(UBYTE which, SBYTE deltax, SBYTE deltay); MODULE ABOOL bounceorb(UBYTE which, SBYTE x, SBYTE y); MODULE SBYTE bsign(SBYTE value); MODULE ABOOL findempty(SBYTE* x, SBYTE* y, ABOOL mode); MODULE SBYTE onlyworm(ABOOL alive); MODULE SBYTE slowdown(SBYTE speed, ABOOL nitro); MODULE SBYTE speedup(SBYTE speed, ABOOL nitro); MODULE UBYTE whichcreature(SBYTE x, SBYTE y, UBYTE species, UBYTE exception); MODULE SBYTE whichteleport(SBYTE x, SBYTE y); MODULE ULONG wormobject(UBYTE player, SBYTE x, SBYTE y); MODULE void wormkillcreature(UBYTE player, UBYTE which); MODULE void createcreature(UBYTE species, UBYTE which, SBYTE x, SBYTE y, SBYTE deltax, SBYTE deltay, UBYTE player); MODULE ULONG arand(ULONG number); MODULE void protcreature(UBYTE player, UBYTE which); MODULE void wormcreature(UBYTE player, UBYTE which); MODULE void creaturecreature(UBYTE which1, UBYTE which2); /* PRIVATE STRUCTURES -------------------------------------------------- */ struct { SBYTE x, y, deltax, deltay; ABOOL alive, moved, teleported, visible, reflected; } bullet[9]; struct { UWORD freq; ULONG score; } object[LASTOBJECT + 1] = { {1600, 60}, // AFFIXER { 65, 20}, // AMMO { 105, 20}, // ARMOUR { 70, 50}, // BIAS { 130, 30}, // BOMB { 110, 10}, // BONUS { 900, 60}, // CLOCK { 370, 50}, // CONVERTER { 285, 80}, // CUTTER { 250, 90}, // CYCLONE { 310, 20}, // ENCLOSER { 280, 30}, // GLOW { 210, 50}, // GROWER {1900, 90}, // HEALER { 950, 60}, // ICE { 140, 60}, // LIFE { 160, 80}, // LIGHTNING { 925, 80}, // MAGNET { 215, 40}, // MISSILE { 635, 50}, // MULTIPLIER { 380, 10}, // NITRO { 235, 30}, // POWER { 470, 50}, // PROTECTOR { 210, 40}, // PULSE { 300, 50}, // PUSHER { 400, 40}, // REMNANTS { 500, 30}, // SIDESHOT { 600, 40}, // SLAYER { 980, 40}, // SLOWER { 730, 70}, // SWITCHER {1400, 120}, // TREASURE {3800, 140} // UMBRELLA }; /* -200 common 220-400 uncommon 420-980 rare 1000+ very rare */ struct { SBYTE x, y, deltax, deltay, relx, rely; ABOOL alive, last, visible; } protector[4][PROTECTORS + 1]; struct { SBYTE deltax, deltay; } thewormqueue[4][WORMQUEUELIMIT + 1]; struct { SBYTE deltax, deltay; } thedogqueue[CREATURES + 1][DOGQUEUELIMIT + 1]; struct { ABOOL alive; SBYTE x, y, player; UBYTE object; } magnet[MAGNETS + 1]; AGLOBAL UBYTE missileframes[4][MISSILEFRAMES + 1] = { { FIRSTMISSILE, FIRSTMISSILEFRAME, FIRSTMISSILEFRAME + 1, FIRSTMISSILEFRAME + 2, FIRSTMISSILEFRAME + 3, FIRSTMISSILEFRAME + 4 }, { FIRSTMISSILE + 1, FIRSTMISSILEFRAME + 5, FIRSTMISSILEFRAME + 6, FIRSTMISSILEFRAME + 7, FIRSTMISSILEFRAME + 8, FIRSTMISSILEFRAME + 9 }, { FIRSTMISSILE + 2, FIRSTMISSILEFRAME + 10, FIRSTMISSILEFRAME + 11, FIRSTMISSILEFRAME + 12, FIRSTMISSILEFRAME + 13, FIRSTMISSILEFRAME + 14, }, { FIRSTMISSILE + 3, FIRSTMISSILEFRAME + 15, FIRSTMISSILEFRAME + 16, FIRSTMISSILEFRAME + 17, FIRSTMISSILEFRAME + 18, FIRSTMISSILEFRAME + 19 } }; UBYTE eachworm[4][2][9] = { { { GREENHEAD_NW, GREENHEADUP, GREENHEAD_NE, GREENHEADLEFT, ANYTHING, GREENHEADRIGHT, GREENHEAD_SW, GREENHEADDOWN, GREENHEAD_SE }, { GREENGLOW_NW, GREENGLOWUP, GREENGLOW_NE, GREENGLOWLEFT, ANYTHING, GREENGLOWRIGHT, GREENGLOW_SW, GREENGLOWDOWN, GREENGLOW_SE } }, { { REDHEAD_NW, REDHEADUP, REDHEAD_NE, REDHEADLEFT, ANYTHING, REDHEADRIGHT, REDHEAD_SW, REDHEADDOWN, REDHEAD_SE }, { REDGLOW_NW, REDGLOWUP, REDGLOW_NE, REDGLOWLEFT, ANYTHING, REDGLOWRIGHT, REDGLOW_SW, REDGLOWDOWN, REDGLOW_SE } }, { { BLUEHEAD_NW, BLUEHEADUP, BLUEHEAD_NE, BLUEHEADLEFT, ANYTHING, BLUEHEADRIGHT, BLUEHEAD_SW, BLUEHEADDOWN, BLUEHEAD_SE }, { BLUEGLOW_NW, BLUEGLOWUP, BLUEGLOW_NE, BLUEGLOWLEFT, ANYTHING, BLUEGLOWRIGHT, BLUEGLOW_SW, BLUEGLOWDOWN, BLUEGLOW_SE } }, { { YELLOWHEAD_NW, YELLOWHEADUP, YELLOWHEAD_NE, YELLOWHEADLEFT,ANYTHING, YELLOWHEADRIGHT, YELLOWHEAD_SW, YELLOWHEADDOWN,YELLOWHEAD_SE }, { YELLOWGLOW_NW, YELLOWGLOWUP, YELLOWGLOW_NE, YELLOWGLOWLEFT,ANYTHING, YELLOWGLOWRIGHT, YELLOWGLOW_SW, YELLOWGLOWDOWN,YELLOWGLOW_SE } } }; UBYTE eachtail[4][2][9][9] = { { { { GN_SE_NW, GN_SE_N, GN_SE_NE, // going NW (delta -1, -1) GN_SE_W, WHATEVER, GN_SE_E, // (so starting from SE) GN_SE_SW, GN_SE_S, WHATEVER }, { GN_S_NW, GN_S_N, GN_S_NE, // going N (delta 0, -1) GN_S_W, WHATEVER, GN_S_E, // (so starting from S) GN_S_SW, WHATEVER, GN_S_SE }, { GN_SW_NW, GN_SW_N, GN_SW_NE, // going NE (delta 0, 1) GN_SW_W, WHATEVER, GN_SW_E, // (so starting from SW) WHATEVER, GN_SW_S, GN_SW_SE }, { GN_E_NW, GN_E_N, GN_E_NE, // going W (delta -1, 0) GN_E_W, WHATEVER, WHATEVER, // (so starting from E) GN_E_SW, GN_E_S, GN_E_SE }, { GN_SE_NW, GN_S_N, GN_SW_NE, // going nowhere (delta 0, 0) GN_E_W, WHATEVER, GN_W_E, GN_NE_SW, GN_N_S, GN_NW_SE }, { GN_W_NW, GN_W_N, GN_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, GN_W_E, // (so starting from W) GN_W_SW, GN_W_S, GN_W_SE }, { GN_NE_NW, GN_NE_N, WHATEVER, // going SW (delta -1, 1) GN_NE_W, WHATEVER, GN_NE_E, // (so starting from NE) GN_NE_SW, GN_NE_S, GN_NE_SE }, { GN_N_NW, WHATEVER, GN_N_NE, // going S (delta 0, 1) GN_N_W, WHATEVER, GN_N_E, // (so starting from N) GN_N_SW, GN_N_S, GN_N_SE }, { WHATEVER, GN_NW_N, GN_NW_NE, // going SE (delta 1, 1) GN_NW_W, WHATEVER, GN_NW_E, // (so starting from NW) GN_NW_SW, GN_NW_S, GN_NW_SE } }, { { GG_SE_NW, GG_SE_N, GG_SE_NE, // going NW (delta -1, -1) GG_SE_W, WHATEVER, GG_SE_E, // (so starting from SE) GG_SE_SW, GG_SE_S, WHATEVER }, { GG_S_NW, GG_S_N, GG_S_NE, // going N (delta 0, -1) GG_S_W, WHATEVER, GG_S_E, // (so starting from S) GG_S_SW, WHATEVER, GG_S_SE }, { GG_SW_NW, GG_SW_N, GG_SW_NE, // going NE (delta 0, 1) GG_SW_W, WHATEVER, GG_SW_E, // (so starting from SW) WHATEVER, GG_SW_S, GG_SW_SE }, { GG_E_NW, GG_E_N, GG_E_NE, // going W (delta -1, 0) GG_E_W, WHATEVER, WHATEVER, // (so starting from E) GG_E_SW, GG_E_S, GG_E_SE }, { GG_SE_NW, GG_S_N, GG_SW_NE, // going nowhere (delta 0, 0) GG_E_W, WHATEVER, GG_W_E, GG_NE_SW, GG_N_S, GG_NW_SE }, { GG_W_NW, GG_W_N, GG_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, GG_W_E, // (so starting from W) GG_W_SW, GG_W_S, GG_W_SE }, { GG_NE_NW, GG_NE_N, WHATEVER, // going SW (delta -1, 1) GG_NE_W, WHATEVER, GG_NE_E, // (so starting from NE) GG_NE_SW, GG_NE_S, GG_NE_SE }, { GG_N_NW, WHATEVER, GG_N_NE, // going S (delta 0, 1) GG_N_W, WHATEVER, GG_N_E, // (so starting from N) GG_N_SW, GG_N_S, GG_N_SE }, { WHATEVER, GG_NW_N, GG_NW_NE, // going SE (delta 1, 1) GG_NW_W, WHATEVER, GG_NW_E, // (so starting from NW) GG_NW_SW, GG_NW_S, GG_NW_SE } } }, { { { RN_SE_NW, RN_SE_N, RN_SE_NE, // going NW (delta -1, -1) RN_SE_W, WHATEVER, RN_SE_E, // (so starting from SE) RN_SE_SW, RN_SE_S, WHATEVER }, { RN_S_NW, RN_S_N, RN_S_NE, // going N (delta 0, -1) RN_S_W, WHATEVER, RN_S_E, // (so starting from S) RN_S_SW, WHATEVER, RN_S_SE }, { RN_SW_NW, RN_SW_N, RN_SW_NE, // going NE (delta 0, 1) RN_SW_W, WHATEVER, RN_SW_E, // (so starting from SW) WHATEVER, RN_SW_S, RN_SW_SE }, { RN_E_NW, RN_E_N, RN_E_NE, // going W (delta -1, 0) RN_E_W, WHATEVER, WHATEVER, // (so starting from E) RN_E_SW, RN_E_S, RN_E_SE }, { RN_SE_NW, RN_S_N, RN_SW_NE, // going nowhere (delta 0, 0) RN_E_W, WHATEVER, RN_W_E, RN_NE_SW, RN_N_S, RN_NW_SE }, { RN_W_NW, RN_W_N, RN_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, RN_W_E, // (so starting from W) RN_W_SW, RN_W_S, RN_W_SE }, { RN_NE_NW, RN_NE_N, WHATEVER, // going SW (delta -1, 1) RN_NE_W, WHATEVER, RN_NE_E, // (so starting from NE) RN_NE_SW, RN_NE_S, RN_NE_SE }, { RN_N_NW, WHATEVER, RN_N_NE, // going S (delta 0, 1) RN_N_W, WHATEVER, RN_N_E, // (so starting from N) RN_N_SW, RN_N_S, RN_N_SE }, { WHATEVER, RN_NW_N, RN_NW_NE, // going SE (delta 1, 1) RN_NW_W, WHATEVER, RN_NW_E, // (so starting from NW) RN_NW_SW, RN_NW_S, RN_NW_SE } }, { { RG_SE_NW, RG_SE_N, RG_SE_NE, // going NW (delta -1, -1) RG_SE_W, WHATEVER, RG_SE_E, // (so starting from SE) RG_SE_SW, RG_SE_S, WHATEVER }, { RG_S_NW, RG_S_N, RG_S_NE, // going N (delta 0, -1) RG_S_W, WHATEVER, RG_S_E, // (so starting from S) RG_S_SW, WHATEVER, RG_S_SE }, { RG_SW_NW, RG_SW_N, RG_SW_NE, // going NE (delta 0, 1) RG_SW_W, WHATEVER, RG_SW_E, // (so starting from SW) WHATEVER, RG_SW_S, RG_SW_SE }, { RG_E_NW, RG_E_N, RG_E_NE, // going W (delta -1, 0) RG_E_W, WHATEVER, WHATEVER, // (so starting from E) RG_E_SW, RG_E_S, RG_E_SE }, { RG_SE_NW, RG_S_N, RG_SW_NE, // going nowhere (delta 0, 0) RG_E_W, WHATEVER, RG_W_E, RG_NE_SW, RG_N_S, RG_NW_SE }, { RG_W_NW, RG_W_N, RG_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, RG_W_E, // (so starting from W) RG_W_SW, RG_W_S, RG_W_SE }, { RG_NE_NW, RG_NE_N, WHATEVER, // going SW (delta -1, 1) RG_NE_W, WHATEVER, RG_NE_E, // (so starting from NE) RG_NE_SW, RG_NE_S, RG_NE_SE }, { RG_N_NW, WHATEVER, RG_N_NE, // going S (delta 0, 1) RG_N_W, WHATEVER, RG_N_E, // (so starting from N) RG_N_SW, RG_N_S, RG_N_SE }, { WHATEVER, RG_NW_N, RG_NW_NE, // going SE (delta 1, 1) RG_NW_W, WHATEVER, RG_NW_E, // (so starting from NW) RG_NW_SW, RG_NW_S, RG_NW_SE } } }, { { { BN_NW_SE, BN_N_SE, BN_NE_SE, // going NW (delta -1, -1) BN_W_SE, WHATEVER, BN_E_SE, // (so starting from SE) BN_SW_SE, BN_S_SE, WHATEVER }, { BN_NW_S, BN_N_S, BN_NE_S, // going N (delta 0, -1) BN_W_S, WHATEVER, BN_E_S, // (so starting from S) BN_S_SW, WHATEVER, BN_S_SE }, { BN_NW_SW, BN_N_SW, BN_SW_NE, // going NE (delta 0, 1) BN_W_SW, WHATEVER, BN_SW_E, // (so starting from SW) WHATEVER, BN_S_SW, BN_SW_SE }, { BN_NW_E, BN_N_E, BN_E_NE, // going W (delta -1, 0) BN_W_E, WHATEVER, WHATEVER, // (so starting from E) BN_SW_E, BN_E_S, BN_E_SE }, { BN_NW_SE, BN_N_S, BN_SW_NE, // going nowhere (delta 0, 0) BN_W_E, WHATEVER, BN_W_E, BN_NE_SW, BN_N_S, BN_NW_SE }, { BN_W_NW, BN_N_W, BN_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, BN_W_E, // (so starting from W) BN_W_SW, BN_W_S, BN_W_SE }, { BN_NW_NE, BN_N_NE, WHATEVER, // going SW (delta -1, 1) BN_W_NE, WHATEVER, BN_E_NE, // (so starting from NE) BN_NE_SW, BN_NE_S, BN_NE_SE }, { BN_N_NW, WHATEVER, BN_N_NE, // going S (delta 0, 1) BN_N_W, WHATEVER, BN_N_E, // (so starting from N) BN_N_SW, BN_N_S, BN_N_SE }, { WHATEVER, BN_N_NW, BN_NW_NE, // going SE (delta 1, 1) BN_W_NW, WHATEVER, BN_NW_E, // (so starting from NW) BN_NW_SW, BN_NW_S, BN_NW_SE } }, { { BG_NW_SE, BG_N_SE, BG_NE_SE, // going NW (delta -1, -1) BG_W_SE, WHATEVER, BG_E_SE, // (so starting from SE) BG_SW_SE, BG_S_SE, WHATEVER }, { BG_NW_S, BG_N_S, BG_NE_S, // going N (delta 0, -1) BG_W_S, WHATEVER, BG_E_S, // (so starting from S) BG_S_SW, WHATEVER, BG_S_SE }, { BG_NW_SW, BG_N_SW, BG_NE_SW, // going NE (delta 0, 1) BG_W_SW, WHATEVER, BG_SW_E, // (so starting from SW) WHATEVER, BG_S_SW, BG_SW_SE }, { BG_NE_E, BG_N_E, BG_NE_E, // going W (delta -1, 0) BG_W_E, WHATEVER, WHATEVER, // (so starting from E) BG_SW_E, BG_E_S, BG_E_SE }, { BG_NW_SE, BG_N_S, BG_NE_SW, // going nowhere (delta 0, 0) BG_W_E, WHATEVER, BG_W_E, BG_NE_SW, BG_N_S, BG_NW_SE }, { BG_NW_W, BG_N_W, BG_NE_W, // going E (delta 1, 0) WHATEVER, WHATEVER, BG_W_E, // (so starting from W) BG_W_SW, BG_W_S, BG_W_SE }, { BG_NW_NE, BG_N_NE, WHATEVER, // going SW (delta -1, 1) BG_NE_W, WHATEVER, BG_NE_E, // (so starting from NE) BG_NE_SW, BG_NE_S, BG_NE_SE }, { BG_N_NW, WHATEVER, BG_N_NE, // going S (delta 0, 1) BG_N_W, WHATEVER, BG_N_E, // (so starting from N) BG_N_SW, BG_N_S, BG_N_SE }, { WHATEVER, BG_N_NW, BG_NW_NE, // going SE (delta 1, 1) BG_NW_W, WHATEVER, BG_NW_E, // (so starting from NW) BG_NW_SW, BG_NW_S, BG_NW_SE } } }, { { { YN_NW_SE, YN_N_SE, YN_NE_SE, // going NW (delta -1, -1) YN_W_SE, WHATEVER, YN_E_SE, // (so starting from SE) YN_SW_SE, YN_S_SE, WHATEVER }, { YN_NW_S, YN_N_S, YN_NE_S, // going N (delta 0, -1) YN_W_S, WHATEVER, YN_S_E, // (so starting from S) YN_S_SW, WHATEVER, YN_S_SE }, { YN_NW_SW, YN_N_SW, YN_NE_SW, // going NE (delta 0, 1) YN_W_SW, WHATEVER, YN_E_SW, // (so starting from SW) WHATEVER, YN_S_SW, YN_SW_SE }, { YN_NW_E, YN_N_E, YN_NE_E, // going W (delta -1, 0) YN_W_E, WHATEVER, WHATEVER, // (so starting from E) YN_E_SW, YN_S_E, YN_E_SE }, { YN_NW_SE, YN_N_S, YN_NE_SW, // going nowhere (delta 0, 0) YN_W_E, WHATEVER, YN_W_E, YN_NE_SW, YN_N_S, YN_NW_SE }, { YN_NW_W, YN_N_W, YN_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, YN_W_E, // (so starting from W) YN_W_SW, YN_W_S, YN_W_SE }, { YN_NW_NE, YN_N_NE, WHATEVER, // going SW (delta -1, 1) YN_W_NE, WHATEVER, YN_NE_E, // (so starting from NE) YN_NE_SW, YN_NE_S, YN_NE_SE }, { YN_N_NW, WHATEVER, YN_N_NE, // going S (delta 0, 1) YN_N_W, WHATEVER, YN_N_E, // (so starting from N) YN_N_SW, YN_N_S, YN_N_SE }, { WHATEVER, YN_N_NW, YN_NW_NE, // going SE (delta 1, 1) YN_NW_W, WHATEVER, YN_NW_E, // (so starting from NW) YN_NW_SW, YN_NW_S, YN_NW_SE } }, { { YG_NW_SE, YG_N_SE, YG_NE_SE, // going NW (delta -1, -1) YG_W_SE, WHATEVER, YG_E_SE, // (so starting from SE) YG_SW_SE, YG_S_SE, WHATEVER }, { YG_S_NW, YG_N_S, YG_S_NE, // going N (delta 0, -1) YG_W_S, WHATEVER, YG_E_S, // (so starting from S) YG_S_SW, WHATEVER, YG_S_SE }, { YG_NW_SW, YG_N_SW, YG_NE_SW, // going NE (delta 0, 1) YG_W_SW, WHATEVER, YG_E_SW, // (so starting from SW) WHATEVER, YG_S_SW, YG_SW_SE }, { YG_E_NW, YG_E_N, YG_E_NE, // going W (delta -1, 0) YG_W_E, WHATEVER, WHATEVER, // (so starting from E) YG_E_SW, YG_E_S, YG_E_SE }, { YG_NW_SE, YG_N_S, YG_NE_SW, // going nowhere (delta 0, 0) YG_W_E, WHATEVER, YG_W_E, YG_NE_SW, YG_N_S, YG_NW_SE }, { YG_W_NW, YG_N_W, YG_W_NE, // going E (delta 1, 0) WHATEVER, WHATEVER, YG_W_E, // (so starting from W) YG_W_SW, YG_W_S, YG_W_SE }, { YG_NW_NE, YG_N_NE, WHATEVER, // going SW (delta -1, 1) YG_W_NE, WHATEVER, YG_E_NE, // (so starting from NE) YG_NE_SW, YG_S_NE, YG_NE_SE }, { YG_N_NW, WHATEVER, YG_N_NE, // going S (delta 0, 1) YG_N_W, WHATEVER, YG_E_N, // (so starting from N) YG_N_SW, YG_N_S, YG_N_SE }, { WHATEVER, YG_N_NW, YG_NW_NE, // going SE (delta 1, 1) YG_W_NW, WHATEVER, YG_E_NW, // (so starting from NW) YG_NW_SW, YG_S_NW, YG_NW_SE } } } }; /* Rules for variable types: SBYTE is used for field coordinates and queue indexes UBYTE is used for field contents SWORD is used for frequencies ULONG is used for scores */ struct { ABOOL alive, visible; SBYTE x, y, deltax, deltay, pos, time, dir; UBYTE dormant, multi, speed, last, oldlast, species, hardness, frame, journey, going, then, type; // type is worm 0-3 (for drips, missiles, dogs and birds) SWORD freq; ULONG score; } creature[CREATURES + 1]; // MODULE VARIABLES (used only within engine.c) --------------------------- MODULE ABOOL banging = FALSE, first = TRUE, letters[LETTERS + 1], enclosed = FALSE, trainer; MODULE UBYTE infector[FIELDX + 1][FIELDY + 1], ice, leveltype; MODULE SBYTE letterx, lettery, number, treasurer; // GLOBAL VARIABLES ------------------------------------------------------- ABOOL anims = TRUE, clearthem = FALSE, modified = FALSE, randomflag = FALSE, randomarray[MAXLEVELS + 1], thick = FALSE, turbo = FALSE; UBYTE board[MAXLEVELS + 1][FIELDX + 1][FIELDY + 1], field[FIELDX + 1][FIELDY + 1]; SBYTE a = GAMEOVER, players, level = 1, levels, reallevel, sourcelevel, startx[MAXLEVELS + 1], starty[MAXLEVELS + 1]; SWORD secondsleft, secondsperlevel; TEXT pathname[81], date[DATELENGTH + 1], times[TIMELENGTH + 1]; ULONG delay, r; AGLOBAL struct HiScoreStruct hiscore[HISCORES + 1]; AGLOBAL struct WormStruct worm[4]; AGLOBAL struct TeleportStruct teleport[MAXLEVELS + 1][4]; MODULE ABOOL blocked(UBYTE which, SBYTE deltax, SBYTE deltay) { UBYTE c = field[xwrap(teleport[level][partner(which)].x + deltax)][ywrap(teleport[level][partner(which)].y + deltay)]; if (c == STONE || c == METAL || c == GOAT || c == OCTOPUS) return TRUE; else return FALSE; } MODULE void bombblast(SBYTE triggerer, SBYTE player, SBYTE centrex, SBYTE centrey) { SBYTE counter, downy, downymax, leftx, leftxmax, rightx, rightxmax, strength, uppy, uppymax, x, y; effect(FXUSE_BOMB); strength = ADD_BOMB + arand(RAND_BOMB); leftxmax = centrex - strength; if (leftxmax < 0) leftxmax = 0; rightxmax = centrex + strength; if (rightxmax > FIELDX) rightxmax = FIELDX; uppymax = centrey - strength; if (uppymax < 0) uppymax = 0; downymax = centrey + strength; if (downymax > FIELDY) downymax = FIELDY; leftx = centrex; rightx = centrex; uppy = centrey; downy = centrey; for (counter = 1; counter <= strength; counter++) { if (leftx > leftxmax) { leftx--; for (y = uppy; y <= downy; y++) squareblast(triggerer, player, field[leftx][y], leftx, y, FALSE); } if (uppy > uppymax) { uppy--; for (x = leftx; x <= rightx; x++) squareblast(triggerer, player, field[x][uppy], x, uppy, FALSE); } if (rightx < rightxmax) { rightx++; for (y = downy; y >= uppy; y--) squareblast(triggerer, player, field[rightx][y], rightx, y, FALSE); } if (downy < downymax) { downy++; for (x = rightx; x >= leftx; x--) squareblast(triggerer, player, field[x][downy], x, downy, FALSE); } } } MODULE void bouncegoatoctopus(UBYTE which, SBYTE x, SBYTE y) { UBYTE killed; if (field[x][y] == GOAT || field[x][y] == OCTOPUS || field[x][y] == FISH) { killed = whichcreature(x, y, field[x][y], which); creature[killed].alive = FALSE; change(x, y, BONUS); } } MODULE ABOOL bounceorb(UBYTE which, SBYTE x, SBYTE y) { if ( field[x][y] == METAL || field[x][y] == STONE || field[x][y] == WOOD || field[x][y] == GOAT || field[x][y] == OCTOPUS || field[x][y] == FISH || (field[x][y] >= FIRSTTAIL && field[x][y] <= LASTTAIL) ) { return TRUE; } else { return FALSE; } } MODULE SBYTE bsign(SBYTE value) { if (value < 0) return (-1); elif (value > 0) return (1); else return (0); } MODULE void changefield(void) { SBYTE x, y; if (randomflag && a == PLAYGAME && level) { do { sourcelevel = arand(levels - 1) + 1; } while (randomarray[level]); randomarray[level] = TRUE; } else sourcelevel = level; for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) field[x][y] = board[sourcelevel][x][y]; } void clearhiscores(void) { SBYTE i; clearthem = FALSE; for (i = 0; i <= HISCORES; i++) { hiscore[i].player = -1; hiscore[i].level = 0; hiscore[i].score = 0; hiscore[i].fresh = FALSE; hiscore[i].name[0] = 0; hiscore[i].time[0] = 0; hiscore[i].date[0] = 0; } } MODULE void death(void) { SBYTE pain, player; UBYTE which; ABOOL slow; for (player = 0; player <= 3; player++) { if (worm[player].lives) { if (!worm[player].alive) { slow = FALSE; pain = 0; if (worm[player].cause >= FIRSTTAIL && worm[player].cause <= LASTTAIL) { if (player == worm[player].cause - FIRSTTAIL) pain = PAIN_FRIENDLYTAIL; else pain = PAIN_ENEMYTAIL; slow = TRUE; } elif (worm[player].cause >= FIRSTGLOW && worm[player].cause <= LASTGLOW) { pain = PAIN_GLOW; slow = TRUE; } elif (worm[player].cause >= FIRSTFIRE && worm[player].cause <= LASTFIRE) pain = PAIN_WORMFIRE; elif (worm[player].cause >= FIRSTHEAD && worm[player].cause <= LASTHEAD) pain = PAIN_HEAD; elif (worm[player].cause >= FIRSTPROTECTOR && worm[player].cause <= LASTPROTECTOR) pain = PAIN_PROTECTOR; elif (worm[player].cause >= FIRSTMISSILE && worm[player].cause <= LASTMISSILE) pain = PAIN_MISSILE; elif (worm[player].cause >= FIRSTDRIP && worm[player].cause <= LASTDRIP) pain = PAIN_DRIP; else switch (worm[player].cause) { case GOAT: pain = PAIN_GOAT; slow = TRUE; break; case OCTOPUS: pain = PAIN_OCTOPUS; slow = TRUE; break; case METAL: pain = PAIN_METAL; slow = TRUE; break; case SLIME: pain = PAIN_SLIME; slow = TRUE; break; case STONE: pain = PAIN_STONE; slow = TRUE; break; case TELEPORT: pain = PAIN_TELEPORT; slow = TRUE; break; case WOOD: pain = PAIN_WOOD; slow = TRUE; break; case BIRD: pain = PAIN_BIRD; break; case BOMB: pain = PAIN_BOMB; break; case CLOUD: pain = PAIN_CLOUD; break; case DOG: pain = PAIN_DOG; break; case FRAGMENT: pain = PAIN_FRAGMENT; break; case LIGHTNING: pain = PAIN_LIGHTNING; break; case ORB: pain = PAIN_ORB; break; case OTTER: pain = PAIN_OTTER; break; case PENGUIN: pain = PAIN_PENGUIN; break; case SLAYER: pain = PAIN_SLAYER; break; case WHIRLWIND: pain = PAIN_WHIRLWIND; break; default: // assert(0); break; } if (worm[player].victor >= 0 && worm[player].victor != player) { if (worm[worm[player].victor].bias) { worm[worm[player].victor].lives += pain; stat(worm[player].victor, LIFE); } } if (slow) { worm[player].speed = slowdown(worm[player].speed, worm[player].nitro); stat(player, NITRO); } if (pain > worm[player].lives) worm[player].lives = 0; else worm[player].lives -= pain; draw(worm[player].x, worm[player].y, FIRSTPAIN + player); drawcause(player, NORMAL); stat(player, LIFE); if (level) worm[player].levelreached = level; else worm[player].levelreached = reallevel; if (worm[player].lives) { effect(FXPAIN + player); worm[player].alive = TRUE; worm[player].causewait = r + CAUSEWAIT; } else { /* kill worm */ effect(FXDEATH_WORM); change(worm[player].x, worm[player].y, FIRSTGRAVE + player); updatearrow(worm[player].y); for (which = 0; which <= PROTECTORS; which++) if (protector[player][which].alive && protector[player][which].visible) change(protector[player][which].x, protector[player][which].y, EMPTY); for (which = 0; which <= MAGNETS; which++) if (magnet[which].player == player) magnet[which].alive = FALSE; if (worm[player].score >= worm[player].hiscore) worm[player].hiscore = worm[player].score; } } } } if (!worm[0].lives && !worm[1].lives && !worm[2].lives && !worm[3].lives) { /* End of game */ for (player = 0; player <= 3; player++) if (worm[player].control != NONE && worm[player].score >= worm[player].hiscore) worm[player].hiscore = worm[player].score; newhiscores(); effect(FXGAMEOVER); a = GAMEOVER; if (players == 1) say((STRPTR) "Game over!", worm[onlyworm(FALSE)].colour); elif (worm[0].control && ((!worm[1].control) || worm[1].score < worm[0].score) && ((!worm[2].control) || worm[2].score < worm[0].score) && ((!worm[3].control) || worm[3].score < worm[0].score)) say((STRPTR) "Green wins!", GREEN); elif (worm[1].control && ((!worm[0].control) || worm[0].score < worm[1].score) && ((!worm[2].control) || worm[2].score < worm[1].score) && ((!worm[3].control) || worm[3].score < worm[1].score)) say((STRPTR) "Red wins!", RED); elif (worm[2].control && ((!worm[0].control) || worm[0].score < worm[2].score) && ((!worm[1].control) || worm[1].score < worm[2].score) && ((!worm[3].control) || worm[3].score < worm[2].score)) say((STRPTR) "Blue wins!", BLUE); elif (worm[3].control && ((!worm[0].control) || worm[0].score < worm[3].score) && ((!worm[1].control) || worm[1].score < worm[3].score) && ((!worm[2].control) || worm[2].score < worm[3].score)) say((STRPTR) "Yellow wins!", YELLOW); else say((STRPTR) "A draw!", WHITE); waitasec(); anykey(FALSE); } } MODULE void drawcause(SBYTE player, SBYTE state) { if (state == BLACK) { draw ( -6, (player * 10) + 8, BLACKENED ); } else { draw ( -6, (player * 10) + 8, worm[player].cause ); } } /* NAME enginesetup -- once-only initialization of engine variables SYNOPSIS enginesetup(void); FUNCTION Sets up the unchanging worm variables. MODULE engine.c */ void enginesetup(void) { worm[0].statx = worm[0].staty = worm[1].staty = worm[2].statx = 0; worm[1].statx = worm[2].staty = worm[3].statx = worm[3].staty = 1; worm[0].colour = GREEN; worm[1].colour = RED; worm[2].colour = BLUE; worm[3].colour = YELLOW; worm[0].dimcolour = DARKGREEN; worm[1].dimcolour = DARKRED; worm[2].dimcolour = DARKBLUE; worm[3].dimcolour = DARKYELLOW; worm[0].port = 2; worm[1].port = 3; worm[2].port = 1; worm[3].port = 0; worm[0].name[0] = worm[1].name[0] = worm[2].name[0] = worm[3].name[0] = 0; worm[0].dynamitescore = worm[1].dynamitescore = worm[2].dynamitescore = worm[3].dynamitescore = 0; strcpy(pathname, DEFAULTSET); systemsetup(); } /* NAME fastloop -- things done often SYNOPSIS fastloop(void); FUNCTION Checks for and handles level completion. MODULE engine.c */ MODULE void fastloop(void) { SBYTE i, player, x, y; // all octopi spin together, but some may not be spinning, each may // be in a different part of its spin. if (!arand(FREQ_OCTOPUSSPIN)) { for (i = 0; i <= CREATURES; i++) { if (creature[i].alive && creature[i].species == OCTOPUS && creature[i].dir >= 0) { octopusfire(i); } } } // animate if (anims) { for (i = 0; i <= CREATURES; i++) { if (creature[i].alive && creature[i].visible) { if (creature[i].species == BIRD) { if (!(r % 3)) { creature[i].frame += creature[i].dir; draw(creature[i].x, creature[i].y, BIRD + creature[i].frame); if (creature[i].frame == 0) { creature[i].dir = 1; } elif (creature[i].frame == 4) { creature[i].dir = -1; } } } elif (creature[i].species == MISSILE) { if (!(r % 3)) { drawmissile(creature[i].x, creature[i].y, i); if (++creature[i].frame > MISSILEFRAMES) { creature[i].frame = 0; } } } } } } if (banging) { banging = FALSE; for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == BANGDYNAMITE) { change(x, y, SILVER); bangdynamite(x, y, infector[x][y]); } for (i = 0; i <= 3; i++) if (worm[i].dynamitescore) { wormscore(i, worm[i].dynamitescore); worm[i].dynamitescore = 0; } for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == TEMPBANGDYNAMITE) { banging = TRUE; field[x][y] = BANGDYNAMITE; } } /* DYNAMITE PHILOSOPHY: Worm gets dynamite. Instantly the dynamite infects the surrounding dynamite into bang-dynamite. Each fastloop, you scan the field for bang-dynamite. For each piece you find, it turns to silver and infects any surrounding dynamite. flash letter */ if (level) { if (r % 8 == 1) { draw(letterx, lettery, WHITENED); } elif (r % 8 == 2) { draw(letterx, lettery, FIRSTLETTER + number - 1); } } // flash icons for (player = 0; player <= 3; player++) { icon(player, GLOW); icon(player, CUTTER); } } MODULE ABOOL findempty(SBYTE* x, SBYTE* y, ABOOL mode) { SBYTE count = 0, xx, yy; UBYTE c; if (mode) { do { xx = arand(FIELDX); yy = arand(FIELDY); c = field[xx][yy]; } while ((c != SLIME && c != WOOD && c != STONE && c != METAL && (c < FIRSTTAIL || c > LASTTAIL)) && ++count < PATIENCE); } else { do { xx = arand(FIELDX); yy = arand(FIELDY); c = field[xx][yy]; } while ((c < FIRSTEMPTY || c > LASTEMPTY) && ++count < PATIENCE); } if (count < PATIENCE) { *x = xx; *y = yy; return(TRUE); } else { return(FALSE); } } void gameloop(void) { SBYTE i, player; if (a == PLAYGAME) { ReadGameports(); fastloop(); gameinput(); } if (a == PLAYGAME) { ReadGameports(); for (player = 0; player <= 3; player++) if (worm[player].lives && !(r % worm[player].speed)) wormloop(player); } if (a == PLAYGAME) { ReadGameports(); for (i = 0; i <= CREATURES; i++) { if (creature[i].alive && !(r % creature[i].speed) && (!ice || creature[i].species == MISSILE || creature[i].species == FRAGMENT)) { creatureloop(i); } } } if (a == PLAYGAME) { ReadGameports(); if (!(r % SPEED_MAGNET)) { magnetloop(); } death(); } if (a == PLAYGAME) { ReadGameports(); if (!(r % VERYSLOW)) { slowloop(); } } timing(); } MODULE void killall(void) { UBYTE i; for (i = 0; i <= CREATURES; i++) creature[i].alive = FALSE; for (i = 0; i <= MAGNETS; i++) magnet[i].alive = FALSE; teleport[level][2].alive = FALSE; teleport[level][3].alive = FALSE; } SBYTE loadfields(STRPTR fieldname) { SBYTE i, j, x, y; TEXT IOBuffer[NAMELENGTH + 1]; UBYTE ver; UBYTE oldboard[OLDFIELDX + 1][OLDFIELDY + 1]; /* This routine is not entirely robust, especially regarding failures part way through reading. Also, field data values must be those supported by the field editor (ie. objects, and the squares represented by F1-F8), or undefined behaviour may result. None of this is currently checked for. Provided that the fieldset was created with the official field editor, and the file is not corrupt, these failures should never happen anyway. open file */ // say("Opening...", WHITE); if (!ZOpen(fieldname, FALSE)) return 1; /* no harm done */ /* read header */ // say("Reading header...", WHITE); if (!ZRead(IOBuffer, 10)) { ZClose(); return 2; /* no harm done */ } if (!strcmp(IOBuffer, "FSET 7.0")) ver = 70; elif (!strcmp(IOBuffer, "FSET 6.6")) ver = 66; elif (!strcmp(IOBuffer, "FSET 6.3")) ver = 63; elif (!strcmp(IOBuffer, "FSET 6.2")) ver = 62; elif (!strcmp(IOBuffer, "FSET 6.0")) ver = 60; else { ZClose(); return 3; /* no harm done */ } levels = IOBuffer[9]; /* read high score table */ // say("Reading high score table...", WHITE); for (i = 0; i <= HISCORES; i++) { if (!ZRead(IOBuffer, 6)) { ZClose(); return 4; /* incorrect levels */ } hiscore[i].fresh = FALSE; hiscore[i].player = IOBuffer[0]; hiscore[i].level = IOBuffer[1]; hiscore[i].score = (IOBuffer[2] * 16777216) + (IOBuffer[3] * 65536) + (IOBuffer[4] * 256) + IOBuffer[5]; if (!ZRead(IOBuffer, NAMELENGTH + 1)) { ZClose(); return 5; /* incorrect levels, corrupted high scores */ } for (j = 0; j <= NAMELENGTH; j++) { hiscore[i].name[j] = IOBuffer[j]; } if (!ZRead(IOBuffer, TIMELENGTH + 1)) { ZClose(); return 6; /* incorrect levels, corrupted high scores */ } for (j = 0; j <= TIMELENGTH; j++) { hiscore[i].time[j] = IOBuffer[j]; } if (!ZRead(IOBuffer, DATELENGTH + 1)) { ZClose(); return 7; /* incorrect levels, corrupted high scores */ } for (j = 0; j <= DATELENGTH; j++) { hiscore[i].date[j] = IOBuffer[j]; } } // say("Reading level data...", WHITE); /* read level data */ for (i = 0; i <= levels; i++) { if (!ZRead(IOBuffer, 8)) { ZClose(); return 9; /* incorrect levels, corrupted high scores, incorrect startx, teleports, field data */ } startx[i] = IOBuffer[0]; starty[i] = IOBuffer[1]; teleport[i][0].alive = IOBuffer[2]; teleport[i][0].x = IOBuffer[3]; teleport[i][0].y = IOBuffer[4]; teleport[i][1].alive = IOBuffer[5]; teleport[i][1].x = IOBuffer[6]; teleport[i][1].y = IOBuffer[7]; if (ver <= 66) { if (!ZRead((char *) &oldboard[0][0], OLDLEVELSIZE)) { ZClose(); return 10; /* incorrect levels, corrupted high scores, incorrect startx, teleports, field data */ } for (x = 0; x <= FIELDX; x++) { for (y = 0; y < FIELDY; y++) { board[i][x][y] = oldboard[x][y]; } board[i][x][FIELDY] = EMPTY; } } else { if (!ZRead((char *) &board[i][0][0], LEVELSIZE)) { ZClose(); return 10; /* incorrect levels, corrupted high scores, incorrect startx, teleports, field data */ } } if (ver <= 60) { // convert from FSET 6.0 to FSET 6.2 format for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (board[i][x][y] >= 7) board[i][x][y]++; } if (ver <= 62) { // convert from FSET 6.2 to FSET 6.3/6.6 format for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (board[i][x][y] >= 7) board[i][x][y]++; } } // say("Open done.", WHITE); // no need to read version string ZClose(); modified = FALSE; return 0; } MODULE void magnetloop(void) { SBYTE i; UBYTE c; for (i = 0; i <= MAGNETS; i++) { if (magnet[i].alive) { // defensive programming to ensure magnet is still valid if (field[magnet[i].x][magnet[i].y] != magnet[i].object) { magnet[i].alive = FALSE; } else { change(magnet[i].x, magnet[i].y, EMPTY); magnet[i].x += bsign(worm[magnet[i].player].x - magnet[i].x); magnet[i].y += bsign(worm[magnet[i].player].y - magnet[i].y); c = field[magnet[i].x][magnet[i].y]; if ((c >= FIRSTEMPTY && c <= LASTEMPTY) || (c >= FIRSTTAIL && c <= LASTTAIL)) change(magnet[i].x, magnet[i].y, magnet[i].object); elif (c >= FIRSTHEAD && c <= LASTHEAD) { change(magnet[i].x, magnet[i].y, magnet[i].object); wormscore(c - FIRSTHEAD, wormobject(c - FIRSTHEAD, magnet[i].x, magnet[i].y)); change(magnet[i].x, magnet[i].y, FIRSTHEAD + magnet[i].player); // not entirely the right head image } else magnet[i].alive = FALSE; } } } } AGLOBAL void matchteleports(void) { AUTO UWORD teleports, octopi; AUTO SBYTE x, y, i; say("Checking fieldset...", WHITE); for (i = 0; i <= levels; i++) { teleports = octopi = 0; teleport[level][0].alive = teleport[level][1].alive = FALSE; for (y = 0; y <= FIELDY; y++) { for (x = 0; x <= FIELDX; x++) { if (board[level][x][y] == TELEPORT) { teleports++; if (teleports <= 2) { teleport[level][teleports - 1].x = x; teleport[level][teleports - 1].y = y; } else { board[level][x][y] = EMPTY; } } elif (board[level][x][y] == OCTOPUS) { octopi++; if (octopi > OCTOPI) { board[level][x][y] = EMPTY; } } } } if (teleports == 1) { board[level][teleport[level][0].x][teleport[level][0].y] = EMPTY; } elif (teleports >= 2) { teleport[level][0].alive = teleport[level][1].alive = TRUE; } } } AGLOBAL void newfield(void) { SBYTE x, y; teleport[level][0].alive = FALSE; teleport[level][1].alive = FALSE; startx[level] = CENTREX; starty[level] = CENTREY; if (level) for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) board[level][x][y] = EMPTY; else for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) board[0][x][y] = SILVER; } void newfields(void) { if (verify()) { strcpy(pathname, DEFAULTSET); levels = DEFAULTLEVELS; modified = FALSE; for (level = 0; level <= levels; level++) newfield(); clearhiscores(); level = 1; if (a == FIELDEDIT) { turborender(); saylevel(WHITE); } else hiscores(); } } void newgame(void) { SBYTE i, player; players = 0; for (player = 0; player <= 3; player++) { if (worm[player].control != NONE) players++; worm[player].lives = 0; worm[player].speed = NORMAL; worm[player].hiscore = 0; } for (i = 1; i <= MAXLEVELS; i++) randomarray[i] = FALSE; r = -1; trainer = FALSE; ice = 0; reallevel = 0; level = 1; a = PLAYGAME; clearscreen(); newlevel(arand(3)); timing(); } MODULE void newhiscores(void) { PERSIST TEXT amiganame[4][NAMELENGTH + 1] = {"Jay Miner", "Carl Sassenrath", "R. J. Mical", "Dave Morse"}; AUTO SBYTE i, j, player; datestamp(); for (player = 0; player <= 3; player++) for (i = 0; i <= HISCORES; i++) if (worm[player].control != NONE && worm[player].score >= hiscore[i].score) { /* push all worse hiscores down */ if (i < HISCORES) for (j = HISCORES; j >= i + 1; j--) { hiscore[j].player = hiscore[j - 1].player; hiscore[j].level = hiscore[j - 1].level; hiscore[j].score = hiscore[j - 1].score; hiscore[j].fresh = hiscore[j - 1].fresh; strcpy(hiscore[j].name, hiscore[j - 1].name); strcpy(hiscore[j].date, hiscore[j - 1].date); strcpy(hiscore[j].time, hiscore[j - 1].time); } modified = TRUE; hiscore[i].player = player; hiscore[i].level = worm[player].levelreached; hiscore[i].score = worm[player].hiscore; if (worm[player].control == AMIGA) { strcpy(hiscore[i].name, amiganame[player]); hiscore[i].fresh = FALSE; } else { strcpy(hiscore[i].name, "(New)"); hiscore[i].fresh = TRUE; } strcpy(hiscore[i].time, times); strcpy(hiscore[i].date, date); break; /* vital */ } } MODULE void newlevel(UBYTE player) { SBYTE i, j; UWORD octopi = 0; UBYTE x, y; if (level >= 2) rundown(player); if (a == PLAYGAME) { if (level > levels) { for (i = 0; i <= 3; i++) { if (worm[i].lives) worm[i].levelreached = -1; if (worm[player].control != NONE && worm[player].score >= worm[player].hiscore) worm[player].hiscore = worm[player].score; } celebrate(); newhiscores(); titlescreen(); } else { saylevel(WHITE); for (i = 0; i <= 3; i++) { if (worm[i].multi > 1) { worm[i].multi /= 2; } worm[i].remnants = worm[i].encloser = worm[i].pusher = FALSE; } killall(); changefield(); for (x = 0; x <= FIELDX; x++) { for (y = 0; y <= FIELDY; y++) { if (board[level][x][y] == OCTOPUS) { octopi++; if (octopi <= OCTOPI) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(OCTOPUS, i, x, y, 0, 0, 255); } } } } } } for (i = 0; i <= 3; i++) { worm[i].speed = NORMAL; if (worm[i].lives) stat(i, NITRO); worm[i].moved = FALSE; worm[i].numbers = 0; worm[i].x = startx[sourcelevel]; worm[i].y = starty[sourcelevel]; worm[i].arrowy = worm[i].y; switch(i) { case 0: worm[0].deltax = -1; worm[0].deltay = 0; break; case 1: worm[1].deltax = 1; worm[1].deltay = 0; break; case 2: worm[2].deltax = 0; worm[2].deltay = -1; break; case 3: worm[3].deltax = 0; worm[3].deltay = 1; break; default: break; } } turborender(); delay = atleast(DELAY_MAX - (level * DELAY_DEC), DELAY_MIN); if (level) { secondsperlevel = SECONDSPERLEVEL; number = 1; putnumber(); for (i = 0; i <= 3; i++) { if (!worm[i].lives && worm[i].control != NONE) { /* create (or resurrect) a worm */ worm[i].lives = STARTLIVES; worm[i].score = 0; worm[i].oldscore = 0; worm[i].armour = 0; worm[i].alive = TRUE; worm[i].power = 0; worm[i].bias = 0; worm[i].multi = 1; worm[i].victor = -1; worm[i].ammo = 0; worm[i].glow = 0; worm[i].remnants = worm[i].affixer = worm[i].sideshot = worm[i].pusher = worm[i].flashed = worm[i].encloser = worm[i].rammed = worm[i].nitro = FALSE; worm[i].causewait = (ULONG) -1; worm[i].last = FIRSTTAIL + i; worm[i].pos = -1; worm[i].cutter = 0; worm[i].olddeltax = worm[i].olddeltay = 0; for (j = 0; j <= PROTECTORS; j++) { protector[i][j].alive = FALSE; } for (j = 0; j <= LASTOBJECT; j++) { stat(i, j); turbo = FALSE; } } } } for (i = 0; i <= 3; i++) { icon(i, REMNANTS); icon(i, AFFIXER); icon(i, SIDESHOT); icon(i, PUSHER); icon(i, GLOW); icon(i, CUTTER); icon(i, ENCLOSER); } } } clearkybd(); resettime(); } /* Many creatures take advantage of shared characteristics. 11 creature types (orbs, goats, drips, fragments, missiles, penguins, cyclones, dogs, clouds, timebombs, octopus) use the creature structure. Independent of it are worms, protectors, worm bullets and teleports. */ MODULE void creatureloop(SBYTE which) { ABOOL happy = FALSE; UBYTE bestdistance, distance, player, c, i; SBYTE x, y, xx, yy, xxx, yyy, frontx, fronty, rearx, reary, deltax, deltay; x = creature[which].x; y = creature[which].y; if (!valid(x, y)) // defensive programming { creature[which].alive = FALSE; return; /* TEXT temp1[SAYLIMIT + 1], temp2[8]; strcpy(temp1, "BAD CREATURE AT x: "); stci_d(temp2, x); strcat(temp1, temp2); strcat(temp1, ", y: "); stci_d(temp2, y); strcat(temp1, temp2); strcat(temp1, "!"); say(temp1, PURPLE); draw(FIELDX + 1, 0, creature[which].species); // indicates which creature Delay(250); clearkybd(); anykey(FALSE); */ } /* decide whether and where to move */ switch(creature[which].species) { case BIRD: if (creature[which].type == 255) { bestdistance = 255; for (player = 0; player <= 3; player++) { if (worm[player].lives && !worm[player].bias) { xx = abs(worm[player].x - x); yy = abs(worm[player].y - y); if (xx > yy) distance = xx; else distance = yy; if (distance <= DISTANCE_BIRD && distance < bestdistance) { effect(FXBORN_BIRD); bestdistance = distance; creature[which].type = player; } } } } if (creature[which].type != 255) // if swooping { if (worm[creature[which].type].lives && !worm[creature[which].type].bias) { creature[which].deltax = bsign(worm[creature[which].type].x - x); creature[which].deltay = bsign(worm[creature[which].type].y - y); } else { creature[which].type = 255; // return to dormancy creature[which].deltax = creature[which].deltay = 0; } } break; case CLOUD: if (creature[which].x == 0 || creature[which].x == FIELDX) creature[which].deltax = -creature[which].deltax; break; case OTTER: if (secondsleft) { return; } if (creature[which].journey == OTTER_RIGHT) { switch(creature[which].going) { case OTTER_DOWN: if (creature[which].y == FIELDY) { if (creature[which].x == FIELDX) { creature[which].journey = OTTER_LEFT; creature[which].going = OTTER_UP; } else { creature[which].going = OTTER_RIGHT; creature[which].then = OTTER_UP; } } break; case OTTER_RIGHT: creature[which].going = creature[which].then; break; case OTTER_UP: if (creature[which].y == 0) { if (creature[which].x == FIELDX) { creature[which].journey = OTTER_LEFT; creature[which].going = OTTER_DOWN; } else { creature[which].going = OTTER_RIGHT; creature[which].then = OTTER_DOWN; } } break; default: break; } } else { // assert(creature[which].journey == OTTER_LEFT); switch(creature[which].going) { case OTTER_DOWN: if (creature[which].y == FIELDY) { if (creature[which].x == 0) { creature[which].journey = OTTER_RIGHT; creature[which].going = OTTER_UP; } else { creature[which].going = OTTER_LEFT; creature[which].then = OTTER_UP; } } break; case OTTER_LEFT: creature[which].going = creature[which].then; break; case OTTER_UP: if (creature[which].y == 0) { if (creature[which].x == 0) { creature[which].journey = OTTER_RIGHT; creature[which].going = OTTER_DOWN; } else { creature[which].going = OTTER_LEFT; creature[which].then = OTTER_DOWN; } } break; default: // assert(0); break; } } if (creature[which].going == OTTER_RIGHT) { creature[which].deltax = 1; creature[which].deltay = 0; } elif (creature[which].going == OTTER_LEFT) { creature[which].deltax = -1; creature[which].deltay = 0; } elif (creature[which].going == OTTER_UP) { creature[which].deltax = 0; creature[which].deltay = -1; } elif (creature[which].going == OTTER_DOWN) { creature[which].deltax = 0; creature[which].deltay = 1; } break; case TIMEBOMB: /* decrement and explode timebombs */ if (field[x][y] != TIMEBOMB) creature[which].alive = FALSE; else { effect(FXDO_BOMB); creature[which].time--; if (creature[which].time < 0) { creature[which].alive = FALSE; bombblast(BOMB, 0, x, y); change(x, y, EMPTY); } else draw(x, y, ZERO + creature[which].time); } return; // not a bug break; case DOG: /* remove a movement from the dog queue */ if (creature[which].dormant == CHASING) { if (creature[which].pos != -1) { creature[which].deltax = thedogqueue[which][0].deltax; creature[which].deltay = thedogqueue[which][0].deltay; if (--creature[which].pos != -1) { for (i = 0; i <= creature[which].pos; i++) { thedogqueue[which][i].deltax = thedogqueue[which][i + 1].deltax; thedogqueue[which][i].deltay = thedogqueue[which][i + 1].deltay; } } } else creature[which].alive = FALSE; } break; case PENGUIN: do { xx = arand(2) - 1; yy = arand(2) - 1; } while (!valid(x + xx, y + yy)); c = field[x + xx][y + yy]; if (c >= FIRSTEMPTY && c <= LASTEMPTY) { creature[which].deltax = xx; creature[which].deltay = yy; } else { creature[which].deltax = 0; creature[which].deltay = 0; } break; case WHIRLWIND: /* Whirlwinds have a slight upwards drift. Higher values of WEIGHT make it less buoyant. */ creature[which].deltax = arand(2) - 1; if (!arand(WEIGHT)) creature[which].deltay = arand(1) - 1; else creature[which].deltay = arand(2) - 1; break; case MISSILE: bestdistance = 255; for (player = 0; player <= 3; player++) { if (creature[which].type != player && worm[player].lives) { xx = abs(worm[player].x - x); yy = abs(worm[player].y - y); if (xx < yy) distance = xx; else distance = yy; if (distance < bestdistance) { bestdistance = distance; creature[which].deltax = bsign(worm[player].x - x); creature[which].deltay = bsign(worm[player].y - y); } } for (i = 0; i <= CREATURES; i++) { if (creature[i].alive && which != i) { if ( ( creature[i].species != DRIP && creature[i].species != MISSILE ) || creature[i].type != creature[which].type ) { xx = abs(creature[i].x - x); yy = abs(creature[i].y - y); if (xx < yy) distance = xx; else distance = yy; if (distance < bestdistance) { bestdistance = distance; creature[which].deltax = bsign(creature[i].x - x); creature[which].deltay = bsign(creature[i].y - y); } } } } } if (bestdistance == 255) { creature[which].alive = FALSE; change(x, y, EMPTY); } break; case ORB: frontx = xwrap(x + creature[which].deltax); /* look in front */ fronty = ywrap(y + creature[which].deltay); rearx = xwrap(x - creature[which].deltax); /* look behind */ reary = ywrap(y - creature[which].deltay); if (bounceorb(which, frontx, fronty)) { bouncegoatoctopus(which, frontx, fronty); xx = -creature[which].deltax; /* default bounce angle is 180° */ yy = -creature[which].deltay; if (!bounceorb(which, frontx, reary)) { if (bounceorb(which, rearx, fronty)) { bouncegoatoctopus(which, rearx, fronty); xx = creature[which].deltax; } } elif (!bounceorb(which, rearx, fronty)) { bouncegoatoctopus(which, rearx, fronty); yy = creature[which].deltay; } creature[which].deltax = xx; creature[which].deltay = yy; } break; case FISH: do { xx = arand(2) - 1; yy = arand(2) - 1; } while (!valid(x + xx, y + yy)); c = field[x + xx][y + yy]; if (c == SLIME || c == WOOD || c == STONE || c == METAL || (c >= FIRSTTAIL && c <= LASTTAIL)) { creature[which].deltax = xx; creature[which].deltay = yy; creature[which].last = creature[which].oldlast; if (c >= FIRSTTAIL && c <= LASTTAIL) { creature[which].oldlast = c - FIRSTTAIL + FIRSTGLOW; } else creature[which].oldlast = c; } else { creature[which].deltax = creature[which].deltay = 0; } break; case GOAT: /* decide whether to move */ if (!arand(FREQ_GOATMOVE)) { for (xx = x - 1; xx <= x + 1; xx++) { for (yy = y - 1; yy <= y + 1; yy++) { if (valid(xx, yy) && field[xx][yy] >= FIRSTEMPTY && field[xx][yy] <= LASTEMPTY) { happy = TRUE; break; } } } } creature[which].deltax = creature[which].deltay = 0; if (!happy) { xx = arand(2) - 1; yy = arand(2) - 1; if (valid(x + xx, y + yy) && (xx || yy)) { c = field[x + xx][y + yy]; if (c == SLIME || c == WOOD || c == STONE || c == METAL || (c >= FIRSTTAIL && c <= LASTTAIL)) { creature[which].last = creature[which].oldlast; creature[which].oldlast = c; creature[which].deltax = xx; creature[which].deltay = yy; } } } break; default: break; } /* now move */ if (creature[which].deltax || creature[which].deltay) { if (creature[which].visible) { /* erase previous image */ change(x, y, creature[which].last); if (creature[which].species == OTTER) { if ((worm[0].lives && worm[0].bias) || (worm[1].lives && worm[1].bias) || (worm[2].lives && worm[2].bias) || (worm[3].lives && worm[3].bias)) { creature[which].last = DYNAMITE; } else creature[which].last = STONE; } else { creature[which].last = EMPTY; } } if (creature[which].alive) { creature[which].x += creature[which].deltax; creature[which].y += creature[which].deltay; if (creature[which].species == ORB) { creature[which].x = xwrap(creature[which].x); creature[which].y = ywrap(creature[which].y); } elif ( creature[which].species == DOG || creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == WHIRLWIND ) { if (!valid(creature[which].x, creature[which].y)) { creature[which].alive = FALSE; } } } } creature[which].visible = TRUE; x = creature[which].x; y = creature[which].y; /* Collision detection. */ if ( creature[which].alive && creature[which].species != FISH && creature[which].species != GOAT && creature[which].species != PENGUIN && creature[which].species != OCTOPUS && creature[which].species != TIMEBOMB && (creature[which].deltax || creature[which].deltay) ) { c = field[x][y]; if (c >= FIRSTHEAD && c <= LASTHEAD) { wormcreature(c - FIRSTHEAD, which); } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR) { protcreature(c - FIRSTPROTECTOR, which); } elif (c >= FIRSTTAIL && c <= LASTTAIL) { ; } elif (c >= FIRSTLETTER && c <= LASTLETTER) { creature[which].alive = FALSE; } elif (c == TIMEBOMB) { if (creature[which].species != OTTER) { creature[whichcreature(x, y, TIMEBOMB, which)].alive = FALSE; } bombblast(BOMB, 0, x, y); change(x, y, EMPTY); } elif (c >= FIRSTDRIP && c <= LASTDRIP) { i = whichcreature(x, y, DRIP, which); creaturecreature(i, which); } elif (c >= FIRSTMISSILE && c <= LASTMISSILE) { i = whichcreature(x, y, MISSILE, which); creaturecreature(which, i); } elif ( c == BIRD || c == DOG || c == CLOUD || c == FISH || c == FRAGMENT || c == GOAT || c == OCTOPUS || c == ORB || c == OTTER || c == PENGUIN || c == WHIRLWIND ) { i = whichcreature(x, y, c, which); creaturecreature(which, i); } elif (c == METAL) { if (creature[which].species == DRIP || creature[which].species == MISSILE || creature[which].species == CLOUD) creature[which].alive = FALSE; elif (creature[which].species == FRAGMENT) reflect(which); } elif (c == STONE || c == WOOD) { if (creature[which].species == DRIP || creature[which].species == FRAGMENT || creature[which].species == MISSILE || creature[which].species == CLOUD) { effect(FXDEATH_FRAGMENT); creature[which].alive = FALSE; } } elif (c == DYNAMITE) { ; } elif (c >= FIRSTGRAVE && c <= LASTGRAVE) { if (creature[which].species == ORB) { effect(FXGET_SKULL); } elif (creature[which].species == FRAGMENT || creature[which].species == DRIP || creature[which].species == MISSILE || creature[which].species == CLOUD) { effect(FXDEATH_FRAGMENT); creature[which].alive = FALSE; } } elif (c == TELEPORT) { /* Drips, fragments, missiles, orbs, whirlwinds, dogs, clouds, otters */ i = whichteleport(x, y); if (creature[which].species != OTTER && blocked(i, creature[which].deltax, creature[which].deltay)) creature[which].alive = FALSE; else { effect(FXUSE_TELEPORT); creature[which].x = teleport[level][partner(i)].x + creature[which].deltax; creature[which].y = teleport[level][partner(i)].y + creature[which].deltay; if (creature[which].species == ORB) { creature[which].x = xwrap(creature[which].x); creature[which].y = ywrap(creature[which].y); } else { if (!(valid(creature[which].x, creature[which].y))) creature[which].alive = FALSE; if (creature[which].species == FRAGMENT) creature[which].last = SILVER; } } } elif (creature[which].species == ORB) { if (c <= LASTOBJECT) { if ( c == AMMO || c == CYCLONE || c == LIGHTNING || c == NITRO || c == POWER || c == PULSE || c == SLAYER || c == SLOWER || c == ENCLOSER ) { effect(FXGET_NITRO); creature[which].speed = speedup(creature[which].speed, TRUE); } elif (c == HEALER || c == LIFE || c == ICE || c == TREASURE || c == UMBRELLA || c == BONUS || c == ARMOUR) { orbsplit(which); } else switch (c) { case BOMB: draw(x, y, ORB); bombblast(ORB, which, x, y); break; case PROTECTOR: for (player = 0; player <= 3; player++) { if (worm[player].lives) { for (i = 0; i <= PROTECTORS; i++) { if (protector[player][i].alive) { protector[player][i].alive = FALSE; if (protector[player][i].visible) { change(protector[player][i].x, protector[player][i].y, EMPTY); } } } } } break; case MISSILE: case CONVERTER: effect(FXGET_OBJECT); for (i = 0; i <= CREATURES; i++) { if (creature[i].alive && creature[i].species == MISSILE) { creature[i].alive = FALSE; change(x, y, EMPTY); } } break; case MULTIPLIER: effect(FXGET_OBJECT); creature[which].multi *= 2; if (creature[which].multi > MULTILIMIT) creature[which].multi = MULTILIMIT; break; case BIAS: effect(FXGET_OBJECT); for (player = 0; player <= 3; player++) if (worm[player].lives && worm[player].bias) { worm[player].bias = 0; stat(player, BIAS); } break; case AFFIXER: effect(FXGET_OBJECT); for (player = 0; player <= 3; player++) if (worm[player].lives) { worm[player].affixer = FALSE; icon(player, AFFIXER); } break; case REMNANTS: effect(FXGET_OBJECT); for (player = 0; player <= 3; player++) if (worm[player].lives) { worm[player].remnants = FALSE; icon(player, REMNANTS); } break; case SIDESHOT: effect(FXGET_POWERUP); for (player = 0; player <= 3; player++) if (worm[player].lives) { worm[player].sideshot = FALSE; icon(player, SIDESHOT); } break; case MAGNET: effect(FXGET_OBJECT); for (i = 0; i <= MAGNETS; i++) if (magnet[i].alive) magnet[i].alive = FALSE; break; case PUSHER: effect(FXGET_OBJECT); for (i = 0; i <= 3; i++) if (worm[i].lives && worm[i].pusher) { worm[i].pusher = FALSE; icon(i, PUSHER); } break; case GLOW: effect(FXGET_OBJECT); for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) if (field[xx][yy] >= FIRSTGLOW && field[xx][yy] <= LASTGLOW) change(xx, yy, EMPTY); break; case SWITCHER: effect(FXGET_OBJECT); for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) if (field[xx][yy] >= FIRSTTAIL && field[xx][yy] <= LASTTAIL) change(xx, yy, WOOD); break; case GROWER: effect(FXGET_GROWER); for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) if (field[xx][yy] == WOOD) for (xxx = xx - 1; xxx <= xx + 1; xxx++) for (yyy = yy - 1; yyy <= yy + 1; yyy++) if (valid(xxx, yyy) && field[xxx][yyy] == EMPTY) field[xxx][yyy] = TEMPWOOD; for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) if (field[xx][yy] == TEMPWOOD) change(xx, yy, WOOD); break; case CLOCK: secondsperlevel -= arand(RAND_CLOCK); if (secondsperlevel < 0) secondsperlevel = 0; break; case CUTTER: for (i = 0; i <= 3; i++) if (worm[i].lives && worm[i].cutter) { worm[i].cutter = 0; icon(i, CUTTER); } break; default: // assert(0); break; } } } } x = creature[which].x; /* We refresh these in case a fragment has been */ y = creature[which].y; /* reflected. Yes, it is vital. */ if (creature[which].alive && creature[which].visible && (creature[which].deltax || creature[which].deltay)) { if (creature[which].species == MISSILE) { drawmissile(x, y, which); } elif (creature[which].species == DRIP) { change(x, y, FIRSTDRIP + creature[which].type); } else /* fragments, goats, penguins, cyclones, dogs, clouds, orbs, octopi, fish, otters */ { change(x, y, creature[which].species); } } if (creature[which].alive) { /* decide whether to fire */ if (creature[which].species == GOAT) { if (!arand(FREQ_GOATFIRE)) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { deltax = arand(2) - 1; deltay = arand(2) - 1; if (valid(x + deltax, y + deltay) && (deltax || deltay)) { c = field[x + deltax][y + deltay]; if ( (c >= FIRSTEMPTY && c <= LASTEMPTY) || (c >= FIRSTTAIL && c <= LASTTAIL) ) { effect(FXDO_GOAT); createcreature(FRAGMENT, i, x + deltax, y + deltay, deltax, deltay, 255); } } break; } } } } elif (creature[which].species == CLOUD) { if (!arand(FREQ_CLOUDFIRE)) { cloudbullet(which, x, y, -1); cloudbullet(which, x, y, 1); } } elif (creature[which].species == OCTOPUS) { if (creature[which].dir == -1) { if (!arand(FREQ_OCTOPUSFIRE)) { creature[which].dir = 0; } } } } } MODULE void cloudbullet(UBYTE which, SBYTE x, SBYTE y, SBYTE deltay) { UBYTE i, c; for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { if (valid(x, y + deltay)) { c = field[x][y + deltay]; if ( (c >= FIRSTEMPTY && c <= LASTEMPTY) || (c >= FIRSTTAIL && c <= LASTTAIL) ) { effect(FXDO_CLOUD); createcreature(FRAGMENT, i, x, y + deltay, 0, deltay, 255); } } break; } } } MODULE void orbsplit(SBYTE which) { SBYTE copy = 0, i; effect(FXDO_ORB); for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { creature[i].x = creature[which].x; creature[i].y = creature[which].y; creature[i].score = creature[which].score; creature[i].speed = creature[which].speed; creature[i].multi = creature[which].multi; creature[i].last = EMPTY; creature[i].species = ORB; switch (copy) { case 0: if (creature[which].deltax != -1 || creature[which].deltay != -1) { creature[i].deltax = -1; creature[i].deltay = -1; creature[i].alive = TRUE; } break; case 1: if (creature[which].deltax != 1 || creature[which].deltay != 1) { creature[i].deltax = 1; creature[i].deltay = 1; creature[i].alive = TRUE; } break; case 2: if (creature[which].deltax != 1 || creature[which].deltay != -1) { creature[i].deltax = 1; creature[i].deltay = -1; creature[i].alive = TRUE; } break; case 3: if (creature[which].deltax != -1 || creature[which].deltay != 1) { creature[i].deltax = -1; creature[i].deltay = 1; creature[i].alive = TRUE; } break; default: break; } if (++copy >= 4) return; } } } SBYTE partner(SBYTE which) { if (which % 2 == 0) return((SBYTE) (which + 1)); else return((SBYTE) (which - 1)); } MODULE void putnumber(void) { SBYTE oldlettery = lettery, x, y; ABOOL done; do { done = findempty(&x, &y, FALSE); } while (!done); letterx = x; lettery = y; change(x, y, FIRSTLETTER + number - 1); updatearrow(oldlettery); updatearrow(lettery); } /* NAME queue -- adds a keystroke to the key queue SYNOPSIS name(SBYTE, SBYTE, SBYTE); FUNCTION Adds a keystroke to the in-game key queue. INPUTS player - player that pressed the key deltax - the deltax of the key deltay - the deltay of the key IMPLEMENTATION thewormqueue[] array has WORMQUEUELIMIT as its last index. It is implemented as a FIFO stack rather than LIFO so that the keystrokes are processed in the correct order (that is, the order in which they were pressed). The oldest keystroke is always at index [0], the next oldest at [1], and so on upwards to the newest keystroke, at [worm[player].pos]. Keystrokes are removed from the bottom of the array ([0]), and the rest of the array is shuffled down to fill the gap, so that the contents of [1] go to [0], the contents of [2] go to [1], etc. worm[player].pos is adjusted to always point to the newest entry, which is the 'end' of the queue. MODULE engine.c */ void wormqueue(SBYTE player, SBYTE deltax, SBYTE deltay) { if (worm[player].pos < WORMQUEUELIMIT) { worm[player].pos++; thewormqueue[player][worm[player].pos].deltax = deltax; thewormqueue[player][worm[player].pos].deltay = deltay; } } MODULE void dogqueue(SBYTE which, SBYTE deltax, SBYTE deltay) { if (creature[which].pos < DOGQUEUELIMIT) { creature[which].pos++; thedogqueue[which][creature[which].pos].deltax = deltax; thedogqueue[which][creature[which].pos].deltay = deltay; } else { creature[which].alive = FALSE; change(creature[which].x, creature[which].y, EMPTY); } } MODULE void reflect(UBYTE which) { creature[which].deltax = -creature[which].deltax; creature[which].deltay = -creature[which].deltay; creature[which].x += creature[which].deltax * 2; creature[which].y += creature[which].deltay * 2; } ABOOL savefields(STRPTR fieldname) { SBYTE i, j; TEXT IOBuffer[NAMELENGTH + 1]; matchteleports(); if (!ZOpen(fieldname, TRUE)) return FALSE; /* write header */ strcpy(IOBuffer, "FSET 7.0"); IOBuffer[9] = levels; if (!ZWrite(IOBuffer, 10)) { ZClose(); return FALSE; } /* write high score table */ for (i = 0; i <= HISCORES; i++) { IOBuffer[0] = hiscore[i].player; IOBuffer[1] = hiscore[i].level; IOBuffer[2] = hiscore[i].score / 16777216; IOBuffer[3] = (hiscore[i].score % 16777216) / 65536; IOBuffer[4] = ((hiscore[i].score % 16777216) % 65536) / 256; IOBuffer[5] = ((hiscore[i].score % 16777216) % 65536) % 256; if (!ZWrite(IOBuffer, 6)) { ZClose(); return FALSE; } for (j = 0; j <= NAMELENGTH; j++) IOBuffer[j] = hiscore[i].name[j]; if (!ZWrite(IOBuffer, NAMELENGTH + 1)) { ZClose(); return FALSE; } for (j = 0; j <= TIMELENGTH; j++) IOBuffer[j] = hiscore[i].time[j]; if (!ZWrite(IOBuffer, TIMELENGTH + 1)) { ZClose(); return FALSE; } for (j = 0; j <= DATELENGTH; j++) IOBuffer[j] = hiscore[i].date[j]; if (!ZWrite(IOBuffer, DATELENGTH + 1)) { ZClose(); return FALSE; } } /* write level data */ for (i = 0; i <= levels; i++) { IOBuffer[0] = startx[i]; IOBuffer[1] = starty[i]; IOBuffer[2] = teleport[i][0].alive; IOBuffer[3] = teleport[i][0].x; IOBuffer[4] = teleport[i][0].y; IOBuffer[5] = teleport[i][1].alive; IOBuffer[6] = teleport[i][1].x; IOBuffer[7] = teleport[i][1].y; if (!ZWrite(IOBuffer, 8)) { ZClose(); return FALSE; } if (!ZWrite((char *) &board[i][0][0], LEVELSIZE)) { ZClose(); return FALSE; } } /* write version string */ if (!ZWrite(VERSION, strlen(VERSION))) { ZClose(); return FALSE; } ZClose(); if (clearthem) clearhiscores(); modified = FALSE; return TRUE; } void saylevel(COLOUR colour) { TEXT mainstring[15] = "Level ", tempstring[4]; if (level > 0) { stci_d(&mainstring[6], level); strcat((char*) mainstring, " of "); stci_d(tempstring, levels); strcat((char*) mainstring, (char*) tempstring); say(mainstring, colour); } else { if (a == FIELDEDIT) { say("Bonus Level", colour); } elif (leveltype == TREASURE) { say("Bonus Level: Treasury!", colour); } elif (leveltype == DRIP) { say("Bonus Level: Drips!", colour); } else { // assert(leveltype == PENGUIN); say("Bonus Level: Penguins!", colour); } } } MODULE SBYTE slowdown(SBYTE speed, ABOOL nitro) { speed *= 2; if (nitro) { if (speed > VERYSLOW) speed = VERYSLOW; } elif (speed > SLOW) speed = SLOW; return(speed); } MODULE void slowloop(void) { SBYTE i, player, which, x, xx, y, yy; UBYTE c; ABOOL ok; if (ice) { ice--; } /* decrement worm strength */ for (player = 0; player <= 3; player++) { if (worm[player].lives) { if (worm[player].bias > 0) { worm[player].bias--; stat(player, BIAS); } if (worm[player].cutter > 0) { worm[player].cutter--; icon(player, CUTTER); } if (worm[player].glow > 0) { worm[player].glow--; icon(player, GLOW); } if (worm[player].armour > 0) { worm[player].armour--; stat(player, ARMOUR); } } } /* blank out old causes */ for (player = 0; player <= 3; player++) { if (worm[player].lives > 0 && r > worm[player].causewait) { drawcause(player, BLACK); worm[player].causewait = (ULONG) -1; /* most future time possible */ } } if (!ice) { if (level) { /* create goats */ if ( (!arand(FREQ_GOAT)) && findempty(&x, &y, TRUE) ) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(GOAT, i, x, y, 0, 0, 255); break; } } } /* create octopi */ if ( (!arand(FREQ_OCTOPUS)) && findempty(&x, &y, TRUE) ) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(OCTOPUS, i, x, y, 0, 0, 255); break; } } } /* create fish */ if ( (!arand(FREQ_FISH)) && findempty(&x, &y, TRUE) ) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(FISH, i, x, y, 0, 0, 255); break; } } } /* create orbs */ if (!arand(FREQ_ORB) && findempty(&x, &y, FALSE)) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(ORB, i, x, y, (arand(1) * 2) - 1, (arand(1) * 2) - 1, 255); break; } } } /* create dogs */ if (!arand(FREQ_DOG) && findempty(&x, &y, FALSE)) { for (i = 0; i <= CREATURES; i++) { if (!(creature[i].alive)) { createcreature(DOG, i, x, y, 0, 0, 255); break; } } } /* create slime */ if (!arand(FREQ_SLIME) && findempty(&x, &y, FALSE)) change(x, y, SLIME); /* grow slime */ if (!arand(FREQ_SLIMEGROW)) { for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == SLIME) for (xx = x - 1; xx <= x + 1; xx++) for (yy = y - 1; yy <= y + 1; yy++) if (valid(xx, yy) && field[xx][yy] == EMPTY && !arand(1)) field[xx][yy] = TEMPSLIME; for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == TEMPSLIME) change(x, y, SLIME); } /* create timebombs */ if (!arand(FREQ_TIMEBOMB) && findempty(&x, &y, FALSE)) { for (i = 0; i <= CREATURES; i++) { if (!(creature[i].alive)) { createcreature(TIMEBOMB, i, x, y, 0, 0, 255); break; } } } } /* create drips */ if ( ( level && !arand(FREQ_DRIP)) || (!level && leveltype == DRIP && !arand(FREQ_DRIP / BONUSSPEEDUP)) ) { x = arand(FIELDX); y = arand(2); c = field[x][y]; if (c >= FIRSTEMPTY && c <= LASTEMPTY) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(DRIP, i, x, y, 0, 1, (UBYTE) ((r / 16) % 4)); break; } } } } // create penguins if ( ( ( level && !arand(FREQ_PENGUIN)) || (!level && leveltype == PENGUIN && !arand(FREQ_PENGUIN / BONUSSPEEDUP)) ) && findempty(&x, &y, FALSE) ) { for (i = 0; i <= CREATURES; i++) { if (!(creature[i].alive)) { createcreature(PENGUIN, i, x, y, 0, 0, 255); break; } } } // create birds if ( (level && !arand(FREQ_BIRD) && findempty(&x, &y, FALSE)) ) { // check whether this location is far enough away from the worms ok = TRUE; for (i = 0; i <= 3; i++) { if ( worm[i].lives && abs(worm[i].x - x) < DISTANCE_BIRD * 2 && abs(worm[i].y - y) < DISTANCE_BIRD * 2 ) { ok = FALSE; break; } } if (ok) { for (i = 0; i <= CREATURES; i++) { if (!(creature[i].alive)) { createcreature(BIRD, i, x, y, 0, 0, 255); break; } } } } /* create clouds */ if ( ( ( level && !arand(FREQ_CLOUD)) || (!level && leveltype == CLOUD && !arand(FREQ_CLOUD / BONUSSPEEDUP)) ) && findempty(&x, &y, FALSE) ) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { if (x < FIELDX / 2) { createcreature(CLOUD, i, x, y, -1, 0, 255); } else { createcreature(CLOUD, i, x, y, 1, 0, 255); } break; } } } /* create objects */ for (which = 0; which <= LASTOBJECT; which++) if (level || leveltype != TREASURE || which == TREASURE) { if (!arand(object[which].freq) && findempty(&x, &y, FALSE)) change(x, y, which); } elif (!arand(object[which].freq / 10) && findempty(&x, &y, FALSE)) change(x, y, which); /* create teleports */ if (!arand(FREQ_TELEPORT) && !teleport[level][2].alive && findempty(&(teleport[level][2].x), &(teleport[level][2].y), FALSE) && findempty(&(teleport[level][3].x), &(teleport[level][3].y), FALSE) && (teleport[level][2].x != teleport[level][3].x || teleport[level][2].y != teleport[level][3].y)) { teleport[level][2].alive = TRUE; teleport[level][3].alive = TRUE; change(teleport[level][2].x, teleport[level][2].y, TELEPORT); change(teleport[level][3].x, teleport[level][3].y, TELEPORT); } } } MODULE SBYTE speedup(SBYTE speed, ABOOL nitro) { speed /= 2; if (speed < FAST) speed = FAST; return(speed); } MODULE void squareblast(SBYTE type, SBYTE player, UBYTE c, SBYTE x, SBYTE y, ABOOL cutter) { SBYTE which; UBYTE filler; if (type == HEAD && worm[player].bias) { filler = DYNAMITE; } else filler = EMPTY; if (c <= LASTOBJECT) { if (!cutter) { change(x, y, filler); } } elif ((c >= FIRSTTAIL && c <= LASTTAIL) || c == WOOD || c == SLIME) { change(x, y, filler); } elif (c >= FIRSTHEAD && c <= LASTHEAD) { if (!cutter && (type != HEAD || player != c - FIRSTHEAD)) { if (worm[c - FIRSTHEAD].armour == 0) { worm[c - FIRSTHEAD].cause = BOMB; worm[c - FIRSTHEAD].alive = FALSE; if (type == HEAD) { worm[c - FIRSTHEAD].victor = player; } else { worm[c - FIRSTHEAD].victor = -1; } } else { effect(FXUSE_ARMOUR); } } } elif (c >= FIRSTMISSILE && c <= LASTMISSILE) { which = whichcreature(x, y, MISSILE, 255); if (!cutter) { if (type == HEAD) { if (player != c - FIRSTMISSILE) { wormkillcreature(player, which); change(x, y, filler); } } } else { creature[which].alive = FALSE; change(x, y, filler); } } elif (c >= FIRSTDRIP && c <= LASTDRIP) { if (!cutter) { which = whichcreature(x, y, DRIP, 255); if (type == HEAD) { wormkillcreature(player, which); } else { creature[which].alive = FALSE; } change(x, y, filler); } } elif ( c == CLOUD || c == DOG || c == FISH || c == FRAGMENT || c == GOAT || c == OCTOPUS || c == ORB || c == PENGUIN || c == WHIRLWIND ) { if (!cutter) { which = whichcreature(x, y, c, 255); if (type == HEAD) { wormkillcreature(player, which); } else { creature[which].alive = FALSE; } change(x, y, filler); } } } void timeloop(void) { AUTO TEXT timedisplay[5] = {"#:##"}; AUTO UBYTE i; AUTO SBYTE y; PERSIST ABOOL expired = FALSE; secondsleft = atleast(secondsleft, 0); timedisplay[0] = 48 + (secondsleft / 60); timedisplay[2] = 48 + ((secondsleft % 60) / 10); timedisplay[3] = 48 + ((secondsleft % 60) % 10); if (!level) { say(timedisplay, worm[treasurer].colour); if (!secondsleft) { level = reallevel + 1; secondsleft = SECONDSPERLEVEL; newlevel(treasurer); } } elif (!secondsleft) { if (!expired) { effect(FXSIREN); say("Time expired!", WHITE); expired = TRUE; // create otters for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { for (y = 0; y <= FIELDY - 1; y++) { if (field[0][y] >= FIRSTEMPTY && field[0][y] <= LASTEMPTY) { createcreature(OTTER, i, 0, y, 0, 1, 255); break; } } break; } } for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { for (y = FIELDY - 1; y >= 0; y--) { if (field[FIELDX][y] >= FIRSTEMPTY && field[FIELDX][y] <= LASTEMPTY) { createcreature(OTTER, i, FIELDX, y, 0, -1, 255); break; } } break; } } } } else { expired = FALSE; say(timedisplay, WHITE); } } void train(SCANCODE scancode) { SBYTE x, y; switch(scancode) { case HELP: trainer = !trainer; break; case NUMERICSLASH: /* Complete the level. */ if (trainer) { trainer = FALSE; verynewlevel(); } break; case NUMERICASTERISK: /* field[][] dump, for debugging purposes. */ if (trainer) { trainer = FALSE; say("Field dump...", PURPLE); for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) draw(x, y, field[x][y]); anykey(FALSE); } break; case NUMERICPLUS: if (trainer) { trainer = FALSE; say("Trainer activated!", PURPLE); anykey(FALSE); if (worm[1].lives > 0) { worm[1].lives = LIVESLIMIT; stat(1, LIFE); worm[1].armour = ARMOURLIMIT; stat(1, ARMOUR); worm[1].bias = BIASLIMIT; stat(1, BIAS); worm[1].ammo = AMMOLIMIT; stat(1, AMMO); worm[1].power = POWERLIMIT; stat(1, POWER); worm[1].nitro = TRUE; stat(1, NITRO); worm[1].affixer = TRUE; icon(1, AFFIXER); worm[1].remnants = TRUE; icon(1, REMNANTS); worm[1].sideshot = TRUE; icon(1, SIDESHOT); worm[1].pusher = TRUE; icon(1, PUSHER); worm[1].cutter = 100; icon(1, CUTTER); worm[1].encloser = TRUE; icon(1, ENCLOSER); trainer = FALSE; } } break; default: break; } } MODULE void turnworm(SBYTE player, SBYTE deltax, SBYTE deltay) { if (worm[player].nitro || !deltax || !deltay) { if (worm[player].deltax == deltax && worm[player].deltay == deltay) { worm[player].speed = speedup(worm[player].speed, worm[player].nitro); stat(player, NITRO); } elif (worm[player].deltax == -deltax && worm[player].deltay == -deltay) { worm[player].speed = slowdown(worm[player].speed, worm[player].nitro); stat(player, NITRO); } else { worm[player].deltax = deltax; worm[player].deltay = deltay; } } } SBYTE valid(SBYTE x, SBYTE y) { if (x >= 0 && x <= FIELDX && y >= 0 && y <= FIELDY) return(TRUE); else return(FALSE); } MODULE UBYTE whichcreature(SBYTE x, SBYTE y, UBYTE species, UBYTE exception) { UBYTE i; for (i = 0; i <= CREATURES; i++) if ( creature[i].alive && creature[i].x == x && creature[i].y == y && creature[i].species == species && i != exception ) return i; return 255; /* error code */ } MODULE SBYTE whichteleport(SBYTE x, SBYTE y) { SBYTE which; for (which = 0; which <= 3; which++) if (teleport[level][which].alive && teleport[level][which].x == x && teleport[level][which].y == y) return((SBYTE) which); return((SBYTE) -1); /* error code */ } MODULE void wormbullet(SBYTE player) { ABOOL finished, flag = FALSE, ok = FALSE, lettered = FALSE; SBYTE distance, i, j, x, y; UBYTE c; if (!worm[player].ammo) { stat(player, BONUS); if (worm[player].speed == FAST) distance = DISTANCE_FAST; elif (worm[player].speed == NORMAL) distance = DISTANCE_NORMAL; elif (worm[player].speed == SLOW) distance = DISTANCE_SLOW; else { // assert(worm[player].speed == VERYSLOW); distance = DISTANCE_VERYSLOW; } /* check for metal */ for (i = 1; i < distance; i++) { x = xwrap(worm[player].x + (i * worm[player].deltax)); y = ywrap(worm[player].y + (i * worm[player].deltay)); if (field[x][y] == METAL) flag = TRUE; } if (!flag) { // assert(abs(worm[player].deltax) <= 1 && abs(worm[player].deltay) <= 1); x = xwrap(worm[player].x + (worm[player].deltax * distance)); y = ywrap(worm[player].y + (worm[player].deltay * distance)); c = field[x][y]; if (c == TELEPORT) { i = whichteleport(x, y); if (!blocked(i, worm[player].deltax, worm[player].deltay)) ok = TRUE; } if (ok || ((c < STONE || c > GOAT) && c != METAL)) { effect(FXDO_JUMP); worm[player].deltax *= distance; worm[player].deltay *= distance; } } } else { effect(FXUSE_AMMO); worm[player].ammo--; stat(player, AMMO); if (worm[player].sideshot) { bullet[7].alive = bullet[8].alive = TRUE; bullet[7].teleported = bullet[8].teleported = 0; bullet[7].visible = bullet[8].visible = TRUE; bullet[7].reflected = bullet[8].reflected = FALSE; bullet[7].x = bullet[8].x = worm[player].x; bullet[7].y = bullet[8].y = worm[player].y; if (!worm[player].deltax && worm[player].deltay) { bullet[7].deltax = -1; bullet[8].deltax = 1; bullet[7].deltay = bullet[8].deltay = 0; } elif (worm[player].deltax && !worm[player].deltay) { bullet[7].deltax = bullet[8].deltax = 0; bullet[7].deltay = -1; bullet[8].deltay = 1; } else /* worm is diagonal */ { if (worm[player].deltax == worm[player].deltay) { bullet[7].deltax = 1; bullet[7].deltay = -1; } else { bullet[7].deltax = -1; bullet[7].deltay = -1; } bullet[8].deltax = -bullet[7].deltax; bullet[8].deltay = -bullet[7].deltay; } } for (i = 0; i <= worm[player].power; i++) { bullet[i].alive = TRUE; bullet[i].teleported = 0; bullet[i].visible = TRUE; bullet[i].reflected = FALSE; bullet[i].deltax = worm[player].deltax; bullet[i].deltay = worm[player].deltay; if (i % 2 == 0) distance = i / 2; else distance = -((i + 1) / 2); if (worm[player].deltax == 0) { bullet[i].x = worm[player].x + distance; bullet[i].y = worm[player].y; } elif (worm[player].deltay == 0) { bullet[i].x = worm[player].x; bullet[i].y = worm[player].y + distance; } else { switch (i) { case 0: bullet[i].x = worm[player].x + worm[player].deltax; bullet[i].y = worm[player].y + worm[player].deltay; break; case 1: bullet[i].x = worm[player].x + worm[player].deltax; bullet[i].y = worm[player].y; break; case 2: bullet[i].x = worm[player].x; bullet[i].y = worm[player].y + worm[player].deltay; break; case 3: bullet[i].x = worm[player].x + worm[player].deltax * 2; bullet[i].y = worm[player].y; break; case 4: bullet[i].x = worm[player].x; bullet[i].y = worm[player].y + worm[player].deltay * 2; break; case 5: bullet[i].x = worm[player].x + worm[player].deltax * 2; bullet[i].y = worm[player].y - worm[player].deltay; break; case 6: bullet[i].x = worm[player].x - worm[player].deltax; bullet[i].y = worm[player].y + worm[player].deltay * 2; break; default: break; } } } /* Bullets are now set up. */ finished = FALSE; while (!finished) { finished = TRUE; for (i = 0; i <= 8; i++) { if (bullet[i].alive) { if (bullet[i].visible) { if (worm[player].remnants) change(x, y, FIRSTGLOW + player); else change(x, y, EMPTY); } finished = FALSE; bullet[i].visible = TRUE; if (bullet[i].reflected) { bullet[i].x -= bullet[i].deltax; bullet[i].y -= bullet[i].deltay; } else { bullet[i].x += bullet[i].deltax; bullet[i].y += bullet[i].deltay; } x = bullet[i].x; y = bullet[i].y; c = field[x][y]; if (!(valid(x, y))) bullet[i].alive = FALSE; elif (x == worm[player].x && y == worm[player].y) { /* hit by own bullet */ bullet[i].alive = FALSE; if (worm[player].armour == 0) { worm[player].cause = FIRSTFIRE + player; worm[player].victor = -1; worm[player].alive = FALSE; } } elif (c >= FIRSTHEAD && c <= LASTHEAD) { if (worm[c - FIRSTHEAD].armour == 0) { worm[c - FIRSTHEAD].cause = FIRSTFIRE + player; worm[c - FIRSTHEAD].victor = player; worm[c - FIRSTHEAD].alive = FALSE; } else effect(FXUSE_ARMOUR); bullet[i].alive = FALSE; } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR) { if (player != c - FIRSTPROTECTOR) { effect(FXUSE_PROTECTOR); bullet[i].alive = FALSE; } else bullet[i].visible = FALSE; } elif (c >= FIRSTLETTER && c <= LASTLETTER) { getnumber(player); lettered = TRUE; } elif (c >= FIRSTGRAVE && c <= LASTGRAVE) { bullet[i].alive = FALSE; } elif (c >= FIRSTMISSILE && c <= LASTMISSILE) { if (player != c - FIRSTMISSILE) creature[whichcreature(x, y, MISSILE, 255)].alive = FALSE; else bullet[i].visible = FALSE; } elif (c >= FIRSTDRIP && c <= LASTDRIP) { j = whichcreature(x, y, DRIP, 255); creature[j].alive = FALSE; } else { switch(c) { case BOMB: // sets it off, for your benefit bullet[i].alive = FALSE; bombblast(HEAD, player, x, y); break; case SLIME: case WOOD: // destroys one layer of it bullet[i].alive = FALSE; change(x, y, EMPTY); break; case METAL: if (bullet[i].reflected) bullet[i].alive = FALSE; else { bullet[i].reflected = TRUE; bullet[i].x -= bullet[i].deltax * 2; bullet[i].y -= bullet[i].deltay * 2; } break; case STONE: bullet[i].alive = FALSE; break; case TELEPORT: j = whichteleport(bullet[i].x, bullet[i].y); if (bullet[i].teleported == 2) bullet[i].alive = FALSE; else { effect(FXUSE_TELEPORT); bullet[i].visible = FALSE; bullet[i].teleported++; bullet[i].x = teleport[level][partner(j)].x; bullet[i].y = teleport[level][partner(j)].y; } break; case CLOUD: case DOG: case FISH: case FRAGMENT: case GOAT: case OCTOPUS: case ORB: case PENGUIN: case WHIRLWIND: bullet[i].alive = FALSE; j = whichcreature(x, y, c, 255); wormkillcreature(player, j); change(x, y, EMPTY); break; case TIMEBOMB: bullet[i].alive = FALSE; creature[whichcreature(x, y, TIMEBOMB, 255)].alive = FALSE; bombblast(BOMB, 0, x, y); change(x, y, EMPTY); break; default: break; } } // x and y need this refreshing here x = bullet[i].x; y = bullet[i].y; if (bullet[i].alive && bullet[i].visible) { draw(x, y, FIRSTFIRE + player); } } } } if (lettered) putnumber(); clearkybd(); } } /* NAME wormloop -- controls worms and protectors SYNOPSIS wormloop(SBYTE); FUNCTION Amiga-worm thinking, processing one keystroke from the worm's queue, all the worm's protectors' control, the worm's control. MODULE engine.c */ MODULE void wormloop(SBYTE player) { SBYTE bestx = 0, besty = 0, // to avoid spurious warnings dirx, diry, i, j, thisprot, x, y, index1, index2; SWORD bestgood, good; UBYTE c; /* Amiga worm control Remove a keystroke from the worm queue Move worm (and add a keystroke to the dog queue) Check for enclosure Move protectors Collision detection AI: Amiga worm control. Worm checks ahead, left and right one square. Assigns opinions to those three choices and then takes the appropriate one. Things which slow the worm down are doubly feared; this is to avoid endless ramming situations. */ if (worm[player].control == AMIGA) { if (!arand(50)) wormqueue(player, arand(2) - 1, arand(2) - 1); else { bestgood = -128; for (i = 0; i <= 2; i++) { switch(i) { case 0: dirx = worm[player].deltax; diry = worm[player].deltay; break; case 1: if (worm[player].deltax == 0) /* if going north or south */ { dirx = -1; /* then look west */ diry = 0; } else /* if going east or west */ { dirx = 0; /* then look north */ diry = -1; } break; case 2: if (worm[player].deltax == 0) /* if going north or south */ { dirx = 1; /* then look east */ diry = 0; } else /* if going east or west */ { dirx = 0; /* then look south */ diry = 1; } break; default: dirx = diry = 0; // to prevent spurious warnings break; } c = field[xwrap(worm[player].x + dirx)][ywrap(worm[player].y + diry)]; if (c >= FIRSTLETTER && c <= LASTLETTER) good = POINTS_LETTER; elif (c >= FIRSTHEAD && c <= LASTHEAD) good = -(PAIN_HEAD); elif (c <= LASTOBJECT) good = (SWORD) object[c].score; elif (c == FIRSTPROTECTOR + player) good = POINTS_EMPTY; elif (c >= FIRSTGLOW && c <= LASTGLOW) { if (player == c - FIRSTGLOW) good = -1; else good = -(PAIN_GLOW); } elif (c >= FIRSTMISSILE && c <= LASTMISSILE) { if (player == c - FIRSTMISSILE) good = 0; else good = -(PAIN_MISSILE); } elif (c >= FIRSTDRIP && c <= LASTDRIP) { if (player == c - FIRSTDRIP) good = SCORE_DRIP; else good = -(PAIN_DRIP); } elif (c >= FIRSTGRAVE && c <= LASTGRAVE) { good = POINTS_GRAVE; } elif (c >= FIRSTTAIL && c <= LASTTAIL) { if (worm[player].armour > 10) if (player != c - FIRSTTAIL) good = POINTS_TURNSILVER; else good = 0; elif (player == c - FIRSTTAIL) good = -(PAIN_FRIENDLYTAIL); else good = -(PAIN_ENEMYTAIL); } else switch(c) { case GOLD: good = POINTS_GOLD; break; case SILVER: good = POINTS_SILVER; break; case EMPTY: good = POINTS_EMPTY; break; case SLIME: if (worm[player].armour > 0) good = 0; else good = -(PAIN_SLIME); break; case WOOD: if (worm[player].armour > 0) good = 0; else good = -(PAIN_WOOD); break; case STONE: good = -(PAIN_STONE * 2); break; case METAL: good = -(PAIN_METAL * 2); break; case GOAT: good = -(PAIN_GOAT * 2); break; case CLOUD: good = -(PAIN_CLOUD); break; case DOG: good = -(PAIN_DOG); break; case FISH: good = -(PAIN_FISH); break; case FRAGMENT: good = -(PAIN_FRAGMENT); break; case OCTOPUS: good = -(PAIN_OCTOPUS * 2); break; case ORB: good = -(PAIN_ORB); break; case WHIRLWIND: good = -(PAIN_WHIRLWIND); break; default: good = 0; break; } if (good > bestgood) { bestx = dirx; besty = diry; bestgood = good; } } if (bestgood < -2 && !arand(1)) { // turn in any of the 8 directions, or fire wormqueue ( player, (SBYTE) ((arand(1) * 2) - 1), (SBYTE) ((arand(1) * 2) - 1) ); } elif (bestgood < -1 && !arand(1)) wormqueue(player, 0, 0); elif (bestgood < 0 && !arand(5)) wormqueue(player, 0, 0); elif (bestx != worm[player].deltax || besty != worm[player].deltay) wormqueue(player, bestx, besty); } } /* remove a keystroke from the worm queue */ if (worm[player].pos != -1) { if (first && worm[2].lives && worm[2].control == JOYSTICK) { first = FALSE; /* Yes, this is defensive programming :-( Because the first time the blue worm has joystick control, a bizarre firebutton event seems to occur. */ } else { if (thewormqueue[player][0].deltax == 0 && thewormqueue[player][0].deltay == 0) wormbullet(player); else turnworm(player, thewormqueue[player][0].deltax, thewormqueue[player][0].deltay); } if (--worm[player].pos != -1) { for (i = 0; i <= worm[player].pos; i++) { thewormqueue[player][i].deltax = thewormqueue[player][i + 1].deltax; thewormqueue[player][i].deltay = thewormqueue[player][i + 1].deltay; } } } /* move worm */ if (worm[player].last == FIRSTTAIL + player) { field[worm[player].x][worm[player].y] = FIRSTTAIL + player; if (!thick) { index1 = worm[player].olddeltax + 1 + ( (worm[player].olddeltay + 1) * 3); index2 = bsign(worm[player].deltax) + 1 + ((bsign(worm[player].deltay) + 1) * 3); drawtail(worm[player].x, worm[player].y, eachtail[player][0][index1][index2]); } else { draw(worm[player].x, worm[player].y, FIRSTTAIL + player); } } elif (worm[player].last == FIRSTGLOW + player) { field[worm[player].x][worm[player].y] = FIRSTGLOW + player; if (!thick) { index1 = worm[player].olddeltax + 1 + ( (worm[player].olddeltay + 1) * 3); index2 = bsign(worm[player].deltax) + 1 + ((bsign(worm[player].deltay) + 1) * 3); drawtail(worm[player].x, worm[player].y, eachtail[player][1][index1][index2]); } else { draw(worm[player].x, worm[player].y, FIRSTGLOW + player); } } else { change(worm[player].x, worm[player].y, worm[player].last); } worm[player].x = xwrap(worm[player].x + worm[player].deltax); worm[player].y = ywrap(worm[player].y + worm[player].deltay); if (worm[player].glow) { worm[player].last = FIRSTGLOW + player; } else { worm[player].last = FIRSTTAIL + player; } for (i = 0; i <= CREATURES; i++) { if (creature[i].alive && creature[i].species == DOG && creature[i].dormant > DORMANT && creature[i].type == player) { if (!worm[player].rammed) dogqueue(i, worm[player].deltax, worm[player].deltay); if (creature[i].dormant < CHASING) { creature[i].dormant++; draw(creature[i].x, creature[i].y, DOGAWAKENING); } } } /* The deltas are not changed back to the range of -1..1 until after the dogs have looked at the queue. This enables them to jump properly. */ worm[player].rammed = FALSE; worm[player].deltax = bsign(worm[player].deltax); worm[player].deltay = bsign(worm[player].deltay); worm[player].olddeltax = worm[player].deltax; worm[player].olddeltay = worm[player].deltay; /* check for enclosure ##### #...# #...# . = interior #...# # = tail ####! ! = head */ enclosed = FALSE; for (i = 2; i <= 7; i++) // for each size of interior { for (j = 0; j <= 3; j++) // four times, once for each direction { checkrectangle(j, player, i, i); if (worm[player].encloser) { checkrectangle(j, player, i, i + 1); checkrectangle(j, player, i + 1, i); } } } // move protectors for (i = 0; i <= PROTECTORS; i++) { if (protector[player][i].alive) { if (protector[player][i].visible) { change(protector[player][i].x, protector[player][i].y, protector[player][i].last); } else protector[player][i].visible = TRUE; protector[player][i].last = EMPTY; if (i == NOSE) { protector[player][i].relx = worm[player].deltax * DISTANCE_NOSE; protector[player][i].rely = worm[player].deltay * DISTANCE_NOSE; if (!worm[player].affixer) { if (worm[player].position == -1) worm[player].posidir = 1; elif (worm[player].position == 1) worm[player].posidir = -1; worm[player].position += worm[player].posidir; if (worm[player].deltax == 0) protector[player][i].relx = worm[player].position; elif (worm[player].deltay == 0) protector[player][i].rely = worm[player].position; elif (worm[player].position == -1) protector[player][i].relx = worm[player].deltax * (DISTANCE_NOSE - 1); elif (worm[player].position == 1) protector[player][i].rely = worm[player].deltay * (DISTANCE_NOSE - 1); } } elif (!worm[player].affixer) { if (protector[player][i].relx == 1 && protector[player][i].rely == -1) { protector[player][i].deltax = 0; protector[player][i].deltay = 1; } elif (protector[player][i].relx == 1 && protector[player][i].rely == 1) { protector[player][i].deltax = -1; protector[player][i].deltay = 0; } elif (protector[player][i].relx == -1 && protector[player][i].rely == 1) { protector[player][i].deltax = 0; protector[player][i].deltay = -1; } elif (protector[player][i].relx == -1 && protector[player][i].rely == -1) { protector[player][i].deltax = 1; protector[player][i].deltay = 0; } protector[player][i].relx += protector[player][i].deltax; protector[player][i].rely += protector[player][i].deltay; } protector[player][i].x = worm[player].x + protector[player][i].relx; protector[player][i].y = worm[player].y + protector[player][i].rely; if (!valid(protector[player][i].x, protector[player][i].y)) { protector[player][i].visible = FALSE; } } } // head collision detection wormcol(player, worm[player].x, worm[player].y); // draw head field[worm[player].x][worm[player].y] = FIRSTHEAD + player; drawhead(player, worm[player].x, worm[player].y); updatearrow(worm[player].arrowy); worm[player].arrowy = worm[player].y; updatearrow(worm[player].arrowy); // protector collision detection for (thisprot = 0; thisprot <= PROTECTORS; thisprot++) { if (protector[player][thisprot].alive && protector[player][thisprot].visible) { protcol(player, protector[player][thisprot].x, protector[player][thisprot].y, thisprot); // draw protector if (protector[player][thisprot].alive && protector[player][thisprot].visible) // in case protector has just been killed, etc. { change(protector[player][thisprot].x, protector[player][thisprot].y, FIRSTPROTECTOR + player); } } } if (worm[player].cutter) { // straight ahead x = xwrap(worm[player].x + worm[player].deltax); y = ywrap(worm[player].y + worm[player].deltay); squareblast(HEAD, player, field[x][y], x, y, TRUE); // left if (!worm[player].deltax || !worm[player].deltay) { // if orthagonal x = xwrap(worm[player].x + worm[player].deltay); y = ywrap(worm[player].y - worm[player].deltax); } else // diagonal { if (worm[player].deltax == worm[player].deltay) { x = xwrap(worm[player].x + worm[player].deltax); y = ywrap(worm[player].y - worm[player].deltay); } else { x = xwrap(worm[player].x - worm[player].deltax); y = ywrap(worm[player].y + worm[player].deltay); } } squareblast(HEAD, player, field[x][y], x, y, TRUE); // right if (!worm[player].deltax || !worm[player].deltay) { // if orthagonal x = xwrap(worm[player].x - worm[player].deltay); y = ywrap(worm[player].y + worm[player].deltax); } else // diagonal { if (worm[player].deltax == worm[player].deltay) { x = xwrap(worm[player].x - worm[player].deltax); y = ywrap(worm[player].y + worm[player].deltay); } else { x = xwrap(worm[player].x + worm[player].deltax); y = ywrap(worm[player].y - worm[player].deltay); } } squareblast(HEAD, player, field[x][y], x, y, TRUE); // ahead left if (!worm[player].deltax || !worm[player].deltay) { // if orthagonal if (worm[player].deltax) // if east or west { x = xwrap(worm[player].x + worm[player].deltax); y = ywrap(worm[player].y - worm[player].deltax); } else // north or south { x = xwrap(worm[player].x + worm[player].deltay); y = ywrap(worm[player].y + worm[player].deltay); } } else // diagonal { if (worm[player].deltax == worm[player].deltay) { x = xwrap(worm[player].x + worm[player].deltax); y = worm[player].y; } else { x = worm[player].x; y = ywrap(worm[player].y + worm[player].deltay); } } squareblast(HEAD, player, field[x][y], x, y, TRUE); // ahead right if (!worm[player].deltax || !worm[player].deltay) { // if orthagonal if (worm[player].deltax) // if east or west { x = xwrap(worm[player].x + worm[player].deltax);; y = ywrap(worm[player].y + worm[player].deltax); } else // north or south { x = xwrap(worm[player].x - worm[player].deltay); y = ywrap(worm[player].y + worm[player].deltay); } } else // diagonal { if (worm[player].deltax == worm[player].deltay) { x = worm[player].x; y = ywrap(worm[player].y + worm[player].deltay); } else { x = xwrap(worm[player].x + worm[player].deltax); y = worm[player].y; } } squareblast(HEAD, player, field[x][y], x, y, TRUE); } } MODULE void protcol(SBYTE player, SBYTE x, SBYTE y, SBYTE thisprot) { UBYTE c = field[x][y]; SBYTE i; if (c >= FIRSTHEAD && c <= LASTHEAD) { protworm(x, y, player, c - FIRSTHEAD); } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR) { protprot(x, y, player, c - FIRSTPROTECTOR); } elif (c >= FIRSTTAIL && c <= LASTTAIL) { if (player == c - FIRSTTAIL) { protector[player][thisprot].visible = FALSE; } } elif ( c == STONE || c == WOOD || c == METAL || c == TIMEBOMB || c == TELEPORT || c == FIRSTGLOW + player ) { protector[player][thisprot].visible = FALSE; } elif (c >= FIRSTMISSILE && c <= LASTMISSILE) { i = whichcreature(x, y, MISSILE, 255); protcreature(player, i); } elif (c >= FIRSTDRIP && c <= LASTDRIP) { i = whichcreature(x, y, DRIP, 255); protcreature(player, i); } elif ( c == BIRD || c == CLOUD || c == DOG || c == FRAGMENT || c == ORB || c == PENGUIN || c == WHIRLWIND ) { i = whichcreature(x, y, c, 255); protcreature(player, i); } else { bothcol(player, x, y); } } MODULE void bothcol(SBYTE player, SBYTE x, SBYTE y) { UBYTE c = field[x][y]; if (c >= FIRSTLETTER && c <= LASTLETTER) { getnumber(player); putnumber(); } elif (c <= LASTOBJECT) { wormscore(player, wormobject(player, x, y)); } elif (c >= FIRSTGRAVE && c <= LASTGRAVE) { effect(FXGET_SKULL); wormscore(player, POINTS_GRAVE); worm[player].bias += worm[c - FIRSTGRAVE].bias; if (worm[player].bias > 0) { if (worm[player].bias > BIASLIMIT) worm[player].bias = BIASLIMIT; stat(player, BIAS); worm[c - FIRSTGRAVE].bias = 0; stat(c - FIRSTGRAVE, BIAS); } worm[player].multi *= worm[c - FIRSTGRAVE].multi; if (worm[player].multi > 1) { if (worm[player].multi > MULTILIMIT) worm[player].multi = MULTILIMIT; } worm[player].power += worm[c - FIRSTGRAVE].power; if (worm[player].power > 1) { if (worm[player].power > POWERLIMIT) worm[player].power = POWERLIMIT; stat(player, POWER); worm[c - FIRSTGRAVE].power = 0; stat(c - FIRSTGRAVE, POWER); } worm[player].ammo += worm[c - FIRSTGRAVE].ammo; if (worm[player].ammo > 0) { if (worm[player].ammo > AMMOLIMIT) worm[player].ammo = AMMOLIMIT; stat(player, AMMO); worm[c - FIRSTGRAVE].ammo = 0; stat(c - FIRSTGRAVE, AMMO); } worm[player].armour += worm[c - FIRSTGRAVE].armour; if (worm[player].armour > 0) { if (worm[player].armour > ARMOURLIMIT) worm[player].armour = ARMOURLIMIT; stat(player, ARMOUR); worm[c - FIRSTGRAVE].armour = 0; stat(c - FIRSTGRAVE, ARMOUR); } if (worm[c - FIRSTGRAVE].nitro) { worm[player].nitro = TRUE; stat(player, NITRO); worm[c - FIRSTGRAVE].nitro = FALSE; worm[c - FIRSTGRAVE].speed = NORMAL; stat(c - FIRSTGRAVE, NITRO); } if (worm[c - FIRSTGRAVE].affixer) { worm[player].affixer = TRUE; icon(player, AFFIXER); worm[c - FIRSTGRAVE].affixer = FALSE; icon(c - FIRSTGRAVE, AFFIXER); } if (worm[c - FIRSTGRAVE].remnants) { worm[player].remnants = TRUE; icon(player, REMNANTS); worm[c - FIRSTGRAVE].remnants = FALSE; icon(c - FIRSTGRAVE, REMNANTS); } if (worm[c - FIRSTGRAVE].sideshot) { worm[player].sideshot = TRUE; icon(player, SIDESHOT); worm[c - FIRSTGRAVE].sideshot = FALSE; icon(c - FIRSTGRAVE, SIDESHOT); } if (worm[c - FIRSTGRAVE].pusher) { worm[player].pusher = TRUE; icon(player, PUSHER); worm[c - FIRSTGRAVE].pusher = FALSE; icon(c - FIRSTGRAVE, PUSHER); } if (worm[c - FIRSTGRAVE].encloser) { worm[player].encloser = TRUE; icon(player, ENCLOSER); worm[c - FIRSTGRAVE].encloser = FALSE; icon(c - FIRSTGRAVE, ENCLOSER); } if (worm[c - FIRSTGRAVE].cutter) { worm[player].cutter += worm[c - FIRSTGRAVE].cutter; icon(player, CUTTER); worm[c - FIRSTGRAVE].cutter = 0; icon(c - FIRSTGRAVE, CUTTER); } } else { switch(c) { case EMPTY: wormscore(player, POINTS_EMPTY); break; case SILVER: wormscore(player, POINTS_SILVER); break; case GOLD: wormscore(player, POINTS_GOLD); break; case DYNAMITE: effect(FXUSE_BOMB); banging = TRUE; bangdynamite(x, y, player); wormscore(player, worm[player].dynamitescore); worm[player].dynamitescore = 0; break; default: break; } } } AGLOBAL void wormscore(SBYTE player, ULONG score) { worm[player].score += score * worm[player].multi * players; stat(player, BONUS); } SBYTE xwrap(SBYTE x) { if (x < 0) x += FIELDX + 1; elif (x > FIELDX) x -= FIELDX + 1; return(x); } SBYTE ywrap(SBYTE y) { if (y < 0) y += FIELDY + 1; elif (y > FIELDY) y -= FIELDY + 1; return(y); } MODULE void ramming(SBYTE player) { SBYTE i; worm[player].rammed = TRUE; worm[player].x = xwrap(worm[player].x - worm[player].deltax); worm[player].y = ywrap(worm[player].y - worm[player].deltay); for (i = 0; i <= PROTECTORS; i++) { /* no point checking whether the protectors are alive or dead */ protector[player][i].x -= worm[player].deltax; protector[player][i].y -= worm[player].deltay; } } MODULE SWORD atleast(SWORD value, SWORD minimum) { if (value < minimum) return(minimum); else return(value); } MODULE void __inline change(SBYTE x, SBYTE y, UBYTE image) { // assert(valid(x, y)); field[x][y] = image; draw(x, y, image); } /* WormWars FSET format for fieldset contents and high score table (Amiga and IBM-PC), as follows: header TEXT[] "FSET x.x" (NULL-terminated) SBYTE levels; high score table for (slot = 0; slot <= HISCORES; slot++) { SBYTE hiscore[slot].player, hiscore[slot].level; SLONG hiscore[slot].score; TEXT[] hiscore[slot].name (NULL-terminated) TEXT[] hiscore[slot].time (NULL-terminated) TEXT[] hiscore[slot].date (NULL-terminated) } level data for (level = 0; level <= levels; level++) { SBYTE startx[level], starty[level]; ABOOL teleport[level][0].alive; SBYTE teleport[level][0].x, teleport[level][0].y; ABOOL teleport[level][1].alive; SBYTE teleport[level][1].x, teleport[level][1].y; for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) SBYTE board[level][x][y]; } version string TEXT[] "$VER: Worm Wars x.x (dd.mm.yy) $" (NULL-terminated) */ MODULE SBYTE onlyworm(ABOOL alive) { SBYTE i, theworm = -1, // to prevent spurious warnings worms = 0; for (i = 0; i <= 3; i++) if (worm[i].control != NONE && ((!alive) || worm[i].lives)) { theworm = i; worms++; } if (worms == 1) return (SBYTE) theworm; else return -1; } MODULE void wormkillcreature(UBYTE player, UBYTE which) { /* This is defensive programming. Sometimes squareblast() finds an orb on the field for which there is no corresponding creature (ie. an 'orphan orb'. whichcreature() on such a square will return 255. */ if (which != 255) { wormscore(player, creature[which].score); creature[which].alive = FALSE; if (worm[player].bias) { worm[player].lives++; stat(player, LIFE); } if (creature[which].species == FRAGMENT) { effect(FXDEATH_FRAGMENT); } elif (creature[which].species == DRIP) { effect(FXGET_DRIP); } } } MODULE void wormworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2) { if (worm[which1].armour == 0 && worm[which2].armour == 0) { /* both worms die */ worm[which1].cause = FIRSTHEAD + which2; worm[which1].alive = FALSE; worm[which1].victor = -1; worm[which2].cause = FIRSTHEAD + which1; worm[which2].alive = FALSE; worm[which2].victor = -1; } elif (worm[which1].armour > 0 && worm[which2].armour == 0) { /* 1st worm lives, 2nd worm dies */ worm[which2].cause = FIRSTHEAD + which1; worm[which2].alive = FALSE; worm[which2].victor = which1; } elif (worm[which1].armour == 0 && worm[which2].armour > 0) { /* 1st worm dies, 2nd worm lives */ worm[which1].cause = FIRSTHEAD + which2; worm[which1].alive = FALSE; worm[which1].victor = which2; } } MODULE void protworm(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2) { SBYTE i, j = -1; // to prevent spurious warnings for (i = 0; i <= PROTECTORS; i++) if (protector[which1][i].alive && protector[which1][i].x == x && protector[which1][i].y == y) { j = i; break; } if (which1 != which2) { if (worm[which2].armour == 0) { effect(FXUSE_PROTECTOR); worm[which2].cause = FIRSTPROTECTOR + which1; worm[which2].victor = which1; worm[which2].alive = FALSE; } else { effect(FXUSE_ARMOUR); protector[which1][j].visible = FALSE; } } else { /* protector is over worm's own head; caused by ramming */ protector[which1][j].visible = FALSE; } } MODULE void protprot(SBYTE x, SBYTE y, UBYTE which1, UBYTE which2) { SBYTE i, p1 = -1, p2 = -1; // to prevent spurious warnings /* Find both protectors */ for (i = 0; i <= PROTECTORS; i++) { if (protector[which1][i].alive && protector[which1][i].x == x && protector[which1][i].y == y) p1 = i; if (protector[which2][i].alive && protector[which2][i].x == x && protector[which2][i].y == y) p2 = i; } protector[which1][p1].alive = FALSE; protector[which2][p2].alive = FALSE; change(x, y, EMPTY); } MODULE ULONG wormobject(UBYTE player, SBYTE x, SBYTE y) { AUTO UBYTE c = field[x][y], d; AUTO ULONG score = object[c].score; AUTO UBYTE i, j, generated = 0; AUTO SBYTE xx, xxx, yy, yyy; AUTO ABOOL done; PERSIST UBYTE otherfield[FIELDX + 1][FIELDY + 1]; for (i = 0; i <= MAGNETS; i++) if (magnet[i].alive && x == magnet[i].x && y == magnet[i].y) magnet[i].alive = FALSE; if (!valid(x, y)) // defensive programming { return 0; /* AUTO TEXT temp1[SAYLIMIT + 1], temp2[8]; strcpy(temp1, "BAD OBJECT AT x: "); stci_d(temp2, x); strcat(temp1, temp2); strcat(temp1, ", y: "); stci_d(temp2, y); strcat(temp1, temp2); strcat(temp1, "!"); say(temp1, worm[player].colour); draw(FIELDX + 1, 0, c); // indicates which object Delay(250); clearkybd(); anykey(FALSE); */ } switch(c) { case BONUS: getnumber(player); change(letterx, lettery, FIRSTLETTER + number - 1); updatearrow(lettery); break; case AMMO: effect(FXGET_AMMO); worm[player].ammo += arand(4) + 2; /* 2-6 bullets */ stat(player, AMMO); break; case ARMOUR: effect(FXGET_OBJECT); worm[player].armour += ADD_ARMOUR + arand(RAND_ARMOUR); stat(player, ARMOUR); break; case NITRO: effect(FXGET_NITRO); worm[player].nitro = TRUE; stat(player, NITRO); break; case BOMB: if (worm[player].glow == 0) draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); bombblast(HEAD, player, worm[player].x, worm[player].y); break; case POWER: effect(FXGET_POWERUP); if (worm[player].power < POWERLIMIT) { worm[player].power += 2; stat(player, POWER); } break; case SLAYER: for (i = 0; i <= CREATURES; i++) { if (creature[i].alive) { if ( ( creature[i].species != MISSILE && creature[i].species != DRIP ) || creature[i].type != player ) { wormkillcreature(player, i); change(creature[i].x, creature[i].y, EMPTY); } } } for (i = 0; i <= 3; i++) if (player != i && worm[i].armour == 0) { worm[i].alive = FALSE; worm[i].cause = SLAYER; worm[i].victor = player; } for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == SLIME) change(x, y, EMPTY); break; case PROTECTOR: // create protector done = FALSE; for (i = 0; i <= PROTECTORS; i++) { if (!protector[player][i].alive && !done) { do { protector[player][i].relx = (arand(1) * 2) - 1; protector[player][i].rely = (arand(1) * 2) - 1; for (j = 0; j <= PROTECTORS; j++) { if ( i == NOSE || !protector[player][j].alive || protector[player][j].x != xwrap(worm[player].x + protector[player][i].relx) || protector[player][j].y != ywrap(worm[player].y + protector[player][i].rely) ) // if we can find an area without a preexisting protector { effect(FXBORN_PROTECTOR); done = TRUE; protector[player][i].alive = TRUE; protector[player][i].visible = FALSE; protector[player][i].last = EMPTY; if (i == NOSE) { worm[player].position = -1; } } } } while (!done); } } break; case MISSILE: for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(MISSILE, i, worm[player].x, worm[player].y, 0, 0, player); break; } } break; case LIFE: effect(FXGET_OBJECT); worm[player].lives += arand(4) + 2; /* 2-6 lives */ stat(player, LIFE); break; case MULTIPLIER: effect(FXGET_OBJECT); if (worm[player].multi < MULTILIMIT) worm[player].multi *= 2; break; case BIAS: effect(FXGET_OBJECT); worm[player].bias += ADD_ARMOUR + arand(RAND_ARMOUR); stat(player, BIAS); break; case ICE: effect(FXGET_OBJECT); ice += ADD_ICE + arand(RAND_ICE); break; case GROWER: effect(FXGET_GROWER); /* grow dynamite */ for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == DYNAMITE) for (xx = x - 1; xx <= x + 1; xx++) for (yy = y - 1; yy <= y + 1; yy++) if (valid(xx, yy)) if (field[xx][yy] == EMPTY) field[xx][yy] = TEMPDYNAMITE; /* grow silver */ for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == SILVER) for (xx = x - 1; xx <= x + 1; xx++) for (yy = y - 1; yy <= y + 1; yy++) if (valid(xx, yy)) if (field[xx][yy] == EMPTY || field[xx][yy] == TEMPDYNAMITE) field[xx][yy] = TEMPSILVER; /* grow gold */ for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) if (field[x][y] == GOLD) for (xx = x - 1; xx <= x + 1; xx++) for (yy = y - 1; yy <= y + 1; yy++) if (valid(xx, yy)) if (field[xx][yy] == EMPTY || field[xx][yy] == TEMPDYNAMITE || field[xx][yy] == TEMPSILVER) field[xx][yy] = TEMPGOLD; /* update field */ for (x = 0; x <= FIELDX; x++) for (y = 0; y <= FIELDY; y++) switch (field[x][y]) { case TEMPGOLD: change(x, y, GOLD); break; case TEMPSILVER: change(x, y, SILVER); break; case TEMPDYNAMITE: change(x, y, DYNAMITE); break; default: break; } break; case TREASURE: treasurer = player; if (level) { secondsperlevel = 0; leveltype = arand(2); if (leveltype == 0) { say("Bonus Level: Treasury!", worm[treasurer].colour); leveltype = TREASURE; } elif (leveltype == 1) { say("Bonus Level: Drips!", worm[treasurer].colour); leveltype = DRIP; } else { // assert(leveltype == 2); say("Bonus Level: Penguins!", worm[treasurer].colour); leveltype = PENGUIN; } } secondsperlevel += ADD_TREASURE + arand(RAND_TREASURE); if (level && leveltype != TREASURE) secondsperlevel *= 2; if (secondsperlevel > TIMELIMIT) secondsperlevel = TIMELIMIT; if (level) { stat(player, BONUS); reallevel = level; level = 0; newlevel(player); } break; case AFFIXER: effect(FXGET_OBJECT); worm[player].affixer = TRUE; icon(player, AFFIXER); break; case SWITCHER: effect(FXGET_OBJECT); for (x = 0; x <= FIELDX; x++) { for (y = 0; y <= FIELDY; y++) { if (players >= 2 && field[x][y] >= FIRSTTAIL && field[x][y] <= LASTTAIL && field[x][y] != FIRSTTAIL + player) { change(x, y, FIRSTTAIL + player); } elif (worm[player].bias) { if (field[x][y] >= FIRSTGLOW && field[x][y] <= LASTGLOW && field[x][y] != FIRSTGLOW + player) change(x, y, FIRSTGLOW + player); elif (field[x][y] == SLIME) change(x, y, DYNAMITE); } } } break; case HEALER: effect(FXGET_OBJECT); if (worm[player].lives < 100) worm[player].lives = 100; else worm[player].lives = LIVESLIMIT; stat(player, LIFE); break; case UMBRELLA: level += arand(1) + 1; if (level >= levels) level = levels; // fixed? for (i = 0; i <= LETTERS; i++) letters[i] = TRUE; break; case CLOCK: effect(FXGET_OBJECT); secondsperlevel += ADD_CLOCK + arand(RAND_CLOCK); if (secondsperlevel > TIMELIMIT) { secondsperlevel = TIMELIMIT; } break; case SLOWER: effect(FXGET_OBJECT); for (i = 0; i <= CREATURES; i++) if (creature[i].alive && creature[i].species != MISSILE) creature[i].speed = (SBYTE) atleast(creature[i].speed * 2, VERYFAST); break; case PULSE: effect(FXGET_PULSE); for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive && generated <= 7) { switch (generated) { case 0: xx = 0; yy = -1; break; case 1: xx = 1; yy = -1; break; case 2: xx = 1; yy = 0; break; case 3: xx = 1; yy = 1; break; case 4: xx = 0; yy = 1; break; case 5: xx = -1; yy = 1; break; case 6: xx = -1; yy = 0; break; case 7: xx = -1; yy = -1; break; default: break; } generated++; if (xx != worm[player].deltax || yy != worm[player].deltay) { d = field[x + xx][y + yy]; if ((d >= FIRSTEMPTY && d <= LASTEMPTY) || (d >= FIRSTTAIL && d <= LASTTAIL) || d <= LASTOBJECT) { createcreature(FRAGMENT, i, x + xx, y + yy, xx, yy, 255); } } } } break; case REMNANTS: effect(FXGET_OBJECT); worm[player].remnants = TRUE; icon(player, REMNANTS); break; case SIDESHOT: effect(FXGET_POWERUP); worm[player].sideshot = TRUE; icon(player, SIDESHOT); break; case MAGNET: effect(FXGET_OBJECT); i = 0; field[x][y] = EMPTY; // so that the magnet itself is destroyed for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) if (field[xx][yy] <= LASTOBJECT) { while (magnet[i].alive && i < MAGNETS) i++; if (i > MAGNETS) { break; } else { magnet[i].x = xx; magnet[i].y = yy; magnet[i].object = field[xx][yy]; magnet[i].player = player; magnet[i].alive = TRUE; i++; } } break; case CUTTER: effect(FXGET_OBJECT); worm[player].cutter += ADD_CUTTER + arand(RAND_CUTTER); icon(player, CUTTER); break; case CYCLONE: /* create whirlwind */ x = arand(FIELDX); y = arand(FIELDY - 5) + 5; d = field[x][y]; if (d >= FIRSTEMPTY && d <= LASTEMPTY) { for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { createcreature(WHIRLWIND, i, x, y, 0, 0, 255); break; } } } break; case LIGHTNING: effect(FXGET_OBJECT); for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) otherfield[xx][yy] = EMPTY; for (xx = 0; xx <= FIELDX; xx++) for (yy = 0; yy <= FIELDY; yy++) if (field[xx][yy] == FIRSTTAIL + player) for (xxx = xx - 1; xxx <= xx + 1; xxx++) for (yyy = yy - 1; yyy <= yy + 1; yyy++) if (valid(xxx, yyy)) { d = field[xxx][yyy]; if (d == ORB || d == GOAT || d == MISSILE || d == PENGUIN || d == FISH || d == FRAGMENT || (d >= FIRSTTAIL && d <= LASTTAIL && d != FIRSTTAIL + player) || (d >= FIRSTHEAD && d <= LASTHEAD) || (d >= FIRSTDRIP && d <= LASTDRIP) || d <= LASTOBJECT) { otherfield[xxx][yyy] = TEMPLIGHTNING; draw(xxx, yyy, LIGHTNING); } } for (xx = 0; xx <= FIELDX; xx++) { for (yy = 0; yy <= FIELDY; yy++) { if (otherfield[xx][yy] == TEMPLIGHTNING) { d = field[xx][yy]; if (d >= FIRSTMISSILE && d <= LASTMISSILE) { i = whichcreature(xx, yy, MISSILE, 255); if (player != creature[i].type) { wormkillcreature(player, i); change(xx, yy, EMPTY); } else drawmissile(xx, yy, i); } elif (d >= FIRSTDRIP && d <= LASTDRIP) { i = whichcreature(xx, yy, DRIP, 255); if (player != creature[i].type) { wormkillcreature(player, i); change(xx, yy, EMPTY); } else draw(xx, yy, FIRSTDRIP + player); } else { switch(d) { case CLOUD: case DOG: case FRAGMENT: case GOAT: case OCTOPUS: case ORB: case PENGUIN: case WHIRLWIND: wormkillcreature(player, whichcreature(xx, yy, d, 255)); change(xx, yy, EMPTY); break; default: /* eg. tail */ change(xx, yy, EMPTY); break; } } } } } break; case PUSHER: effect(FXGET_OBJECT); worm[player].pusher = TRUE; icon(player, PUSHER); break; case GLOW: effect(FXGET_OBJECT); worm[player].glow += ADD_GLOW + arand(RAND_GLOW); if (worm[player].glow > GLOWLIMIT) worm[player].glow = GLOWLIMIT; icon(player, GLOW); break; case ENCLOSER: effect(FXGET_OBJECT); worm[player].encloser = TRUE; icon(player, ENCLOSER); break; case CONVERTER: effect(FXGET_OBJECT); for (i = 0; i <= CREATURES; i++) if (creature[i].alive && creature[i].species == FRAGMENT) { xx = creature[i].x; yy = creature[i].y; creature[i].alive = FALSE; change(xx, yy, EMPTY); createcreature(MISSILE, i, xx, yy, 0, 0, player); } break; default: // assert(0); break; } return(score); } void icon(SBYTE player, UBYTE image) { /* Updates one of the boolean icons. The routine checks the status directly. */ SBYTE x = -4, y = (player * 10) + 7; switch(image) { case AFFIXER: if (worm[player].affixer) draw(x, y, AFFIXER); else draw(x, y, BLACKENED); break; case PUSHER: if (worm[player].pusher) draw(x + 1, y, PUSHER); else draw(x + 1, y, BLACKENED); break; case REMNANTS: if (worm[player].remnants) draw(x + 2, y, REMNANTS); else draw(x + 2, y, BLACKENED); break; case SIDESHOT: if (worm[player].sideshot) draw(x, y + 1, SIDESHOT); else draw(x, y + 1, BLACKENED); break; case CUTTER: if (worm[player].cutter) { if (worm[player].cutter < 10) { if ((r % 4) <= 1) { draw(x + 1, y + 1, CUTTER); } else draw(x + 1, y + 1, BLACKENED); } else draw(x + 1, y + 1, CUTTER); } else draw(x + 1, y + 1, BLACKENED); break; case ENCLOSER: if (worm[player].encloser) draw(x + 2, y + 1, ENCLOSER); else draw(x + 2, y + 1, BLACKENED); break; default: break; } } MODULE void wormcol(SBYTE player, SBYTE x, SBYTE y) { ABOOL flag; UBYTE c = field[x][y], d; SBYTE i, xx, yy; ULONG score = 0; if (c >= FIRSTHEAD && c <= LASTHEAD) wormworm(x, y, player, c - FIRSTHEAD); elif (c == TIMEBOMB) { /* push timebomb */ i = whichcreature(x, y, TIMEBOMB, 255); if (valid(x + worm[player].deltax, y + worm[player].deltay)) { d = field[x + worm[player].deltax][y + worm[player].deltay]; if (d <= LASTEMPTY) { creature[i].x += worm[player].deltax; creature[i].y += worm[player].deltay; field[creature[i].x][creature[i].y] = TIMEBOMB; draw(creature[i].x, creature[i].y, ZERO + creature[i].time); } else // oops, you've fucked up! :-( Timebomb explodes! { if (worm[player].glow == 0) draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); bombblast(HEAD, player, worm[player].x, worm[player].y); creature[i].alive = FALSE; } } else { score += creature[i].score; } } elif (c >= FIRSTPROTECTOR && c <= LASTPROTECTOR) protworm(x, y, c - FIRSTPROTECTOR, player); elif (c >= FIRSTMISSILE && c <= LASTMISSILE) { i = whichcreature(x, y, c, 255); wormcreature(player, i); } elif (c >= FIRSTDRIP && c <= LASTDRIP) { i = whichcreature(x, y, DRIP, 255); wormcreature(player, i); } elif ( c == STONE || c == METAL || c == WOOD || c == FISH || c == GOAT || c == OCTOPUS || (c >= FIRSTTAIL && c <= LASTTAIL) ) // if you've hit something that is pushable { flag = TRUE; // flag is whether you deserve to die if (worm[player].pusher) { xx = x + worm[player].deltax; yy = y + worm[player].deltay; if (valid(xx, yy)) { d = field[xx][yy]; if (d <= LASTEMPTY) // if you're pushing the square into a square which // has an object or is empty/silver/gold { flag = FALSE; // then you don't deserve to die if ( c == FISH || c == GOAT || c == OCTOPUS ) { i = whichcreature(x, y, c, 255); creature[i].x = xx; creature[i].y = yy; creature[i].visible = FALSE; } field[xx][yy] = c; draw(xx, yy, c); } } else // if pushing off the field edges { flag = FALSE; // then you don't deserve to die // kill the creature if ( c == FISH || c == GOAT || c == OCTOPUS ) { i = whichcreature(x, y, c, 255); wormkillcreature(player, i); } } } if (flag) // if we deserve to die { if (c >= FIRSTTAIL && c <= LASTTAIL) // if we're pushing/hitting tail { if (worm[player].armour > 0) { effect(FXUSE_ARMOUR); if (players > 1) { if (player == c - FIRSTTAIL) { score += POINTS_TURNSILVER; worm[player].last = SILVER; } else { score += POINTS_TURNGOLD; worm[player].last = GOLD; } } } elif (!enclosed) { worm[player].cause = c; worm[player].alive = FALSE; worm[player].victor = c - FIRSTTAIL; } } elif ( c == FISH || c == GOAT || c == OCTOPUS ) { i = whichcreature(x, y, c, 255); wormcreature(player, i); } else { worm[player].cause = c; worm[player].alive = FALSE; worm[player].victor = -1; if (c != WOOD) { ramming(player); } } } } elif (c == SLIME) { if (worm[player].armour == 0) { worm[player].cause = c; worm[player].alive = FALSE; worm[player].victor = -1; } } elif ( c == BIRD || c == CLOUD || c == DOG || c == FRAGMENT || c == ORB || c == PENGUIN || c == WHIRLWIND ) { i = whichcreature(x, y, c, 255); wormcreature(player, i); } elif (c == TELEPORT) { i = whichteleport(x, y); if (blocked(i, worm[player].deltax, worm[player].deltay)) { worm[player].cause = TELEPORT; worm[player].victor = -1; worm[player].alive = FALSE; ramming(player); } else { effect(FXUSE_TELEPORT); worm[player].x = xwrap(teleport[level][partner(i)].x + worm[player].deltax); worm[player].y = ywrap(teleport[level][partner(i)].y + worm[player].deltay); } } elif (c >= FIRSTGLOW && c <= LASTGLOW) { if (player != c - FIRSTGLOW && worm[player].armour == 0) { worm[player].cause = GLOW; worm[player].victor = c - FIRSTGLOW; worm[player].alive = FALSE; } } else bothcol(player, x, y); wormscore(player, score); } AGLOBAL void drawhead(SBYTE player, SBYTE x, SBYTE y) { if (worm[player].alive) { if (worm[player].glow == 0) { draw(worm[player].x, worm[player].y, eachworm[player][0][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); worm[player].flashed = FALSE; } else { if (worm[player].glow < 10) { if (!worm[player].flashed) { draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); } else draw(worm[player].x, worm[player].y, WHITENED); worm[player].flashed = !worm[player].flashed; } else draw(worm[player].x, worm[player].y, eachworm[player][1][worm[player].deltax + 1 + (worm[player].deltay + 1) * 3]); } } else draw(worm[player].x, worm[player].y, FIRSTPAIN + player); } AGLOBAL void drawsquare(SBYTE x, SBYTE y) { UBYTE which; if (field[x][y] == DOG) { which = whichcreature(x, y, DOG, 255); if (!creature[which].dormant) { draw(x, y, DOGDORMANT); } elif (creature[which].dormant <= CHASING) { draw(x, y, DOGAWAKENING); } else { draw(x, y, DOG); } } elif (field[x][y] >= FIRSTHEAD && field[x][y] <= LASTHEAD) { drawhead(field[x][y] - FIRSTHEAD, x, y); } else { draw(x, y, field[x][y]); } } MODULE void updatearrow(SBYTE arrowy) { SBYTE i, var = -1; /* var of: -2 : many there -1 : nothing there 0-3 : just that worm, FIRSTLETTER-LASTLETTER: just that letter */ for (i = 0; i <= 3; i++) { if ( worm[i].control != NONE && worm[i].y == arrowy && ( worm[i].lives || ( field[worm[i].x][worm[i].y] >= FIRSTGRAVE && field[worm[i].x][worm[i].y] <= LASTGRAVE ) ) ) { if (var == -1) var = i; else var = -2; } } if (lettery == arrowy) if (var == -1) var = FIRSTLETTER + number - 1; else var = -2; if (var == -2) draw(ARROWX, arrowy, ALL); elif (var == -1) draw(ARROWX, arrowy, BLACKENED); elif (var >= FIRSTLETTER && var <= LASTLETTER) draw(ARROWX, arrowy, var); else { // assert(var >= 0 && var <= 3); if (worm[var].lives) draw(ARROWX, arrowy, FIRSTARROW + var); else draw(ARROWX, arrowy, FIRSTGRAVE + var); } } MODULE void __inline bangdynamite(SBYTE x, SBYTE y, SBYTE player) { SBYTE xx, yy; // Infects (turns to bang-dynamite) all surrounding dynamite. for (xx = x - 1; xx <= x + 1; xx++) { for (yy = y - 1; yy <= y + 1; yy++) { if (valid(xx, yy)) { if (field[xx][yy] == DYNAMITE) { field[xx][yy] = TEMPBANGDYNAMITE; worm[player].dynamitescore += POINTS_DYNAMITE; infector[xx][yy] = player; } } } } } MODULE void octopusfire(UBYTE which) { UBYTE i; SBYTE x, y, deltax, deltay; for (i = 0; i <= CREATURES; i++) { if (!creature[i].alive) { switch(creature[which].dir) { case 0: deltax = 0; deltay = -1; break; case 1: deltax = 1; deltay = -1; break; case 2: deltax = 1; deltay = 0; break; case 3: deltax = 1; deltay = 1; break; case 4: deltax = 0; deltay = 1; break; case 5: deltax = -1; deltay = 1; break; case 6: deltax = -1; deltay = 0; break; case 7: deltax = -1; deltay = -1; break; default: break; } x = creature[which].x + deltax; y = creature[which].y + deltay; if (valid(x, y) && field[x][y] >= FIRSTEMPTY && field[x][y] <= LASTEMPTY) { effect(FXGET_PULSE); createcreature(FRAGMENT, i, x, y, deltax, deltay, 255); goto out; } } } out: if (++creature[which].dir == 8) { creature[which].dir = -1; } } MODULE void createcreature(UBYTE species, UBYTE which, SBYTE x, SBYTE y, SBYTE deltax, SBYTE deltay, UBYTE player) { switch(species) { case FRAGMENT: creature[which].speed = SPEED_FRAGMENT; creature[which].score = SCORE_FRAGMENT; creature[which].hardness = HARDNESS_FRAGMENT; creature[which].visible = TRUE; creature[which].last = EMPTY; change(x, y, FRAGMENT); break; case OCTOPUS: creature[which].speed = SPEED_OCTOPUS; creature[which].score = SCORE_OCTOPUS; creature[which].hardness = HARDNESS_OCTOPUS; creature[which].visible = TRUE; creature[which].last = field[x][y]; creature[which].dir = -1; change(x, y, OCTOPUS); break; case FISH: creature[which].speed = SPEED_FISH; creature[which].score = SCORE_FISH; creature[which].hardness = HARDNESS_FISH; creature[which].visible = TRUE; creature[which].last = creature[which].oldlast = field[x][y]; change(x, y, FISH); break; case GOAT: effect(FXBORN_GOAT); creature[which].speed = SPEED_GOAT; creature[which].score = SCORE_GOAT; creature[which].hardness = HARDNESS_GOAT; creature[which].visible = TRUE; creature[which].last = creature[which].oldlast = field[x][y]; change(x, y, GOAT); break; case DOG: creature[which].speed = SPEED_DOG; creature[which].score = SCORE_DOG; creature[which].hardness = HARDNESS_DOG; creature[which].visible = TRUE; creature[which].last = EMPTY; creature[which].pos = -1; creature[which].dormant = DORMANT; /* dormant */ field[x][y] = DOG; draw(x, y, DOGDORMANT); break; case ORB: effect(FXBORN_ORB); creature[which].speed = SPEED_ORB; creature[which].score = SCORE_ORB; creature[which].hardness = HARDNESS_ORB; creature[which].visible = TRUE; creature[which].last = EMPTY; creature[which].multi = 1; change(x, y, ORB); break; case CLOUD: creature[which].speed = SPEED_CLOUD; creature[which].score = SCORE_CLOUD; creature[which].hardness = HARDNESS_CLOUD; creature[which].visible = TRUE; creature[which].last = EMPTY; change(x, y, CLOUD); break; case PENGUIN: effect(FXBORN_PENGUIN); creature[which].speed = SPEED_PENGUIN; creature[which].score = SCORE_PENGUIN; creature[which].hardness = HARDNESS_PENGUIN; creature[which].visible = TRUE; creature[which].last = EMPTY; change(x, y, PENGUIN); break; case TIMEBOMB: creature[which].speed = SPEED_TIMEBOMB; creature[which].score = SCORE_TIMEBOMB; creature[which].hardness = HARDNESS_TIMEBOMB; creature[which].last = EMPTY; creature[which].time = 10; creature[which].visible = TRUE; field[x][y] = TIMEBOMB; draw(x, y, ZERO + 9); break; case DRIP: effect(FXBORN_DRIP); creature[which].speed = SPEED_DRIP; creature[which].score = SCORE_DRIP; creature[which].hardness = HARDNESS_DRIP; creature[which].last = EMPTY; creature[which].type = player; creature[which].visible = TRUE; change(x, y, FIRSTDRIP + creature[which].type); break; case WHIRLWIND: effect(FXGET_CYCLONE); creature[which].speed = SPEED_WHIRLWIND; creature[which].score = SCORE_WHIRLWIND; creature[which].hardness = HARDNESS_WHIRLWIND; creature[which].last = EMPTY; creature[which].visible = TRUE; change(x, y, WHIRLWIND); break; case MISSILE: effect(FXBORN_MISSILE); creature[which].score = SCORE_MISSILE; creature[which].speed = SPEED_MISSILE; creature[which].hardness = HARDNESS_MISSILE; creature[which].visible = FALSE; creature[which].last = EMPTY; creature[which].type = player; creature[which].frame = 0; break; case BIRD: creature[which].speed = SPEED_BIRD; creature[which].score = SCORE_BIRD; creature[which].hardness = HARDNESS_BIRD; creature[which].visible = TRUE; creature[which].last = EMPTY; creature[which].frame = 0; creature[which].dir = 1; creature[which].type = 255; change(x, y, BIRD); break; case OTTER: creature[which].speed = SPEED_OTTER; creature[which].score = SCORE_OTTER; creature[which].hardness = HARDNESS_OTTER; creature[which].visible = TRUE; creature[which].last = STONE; // should really check whether it should be dynamite instead if (x == 0) { creature[which].going = OTTER_DOWN; creature[which].journey = OTTER_RIGHT; } else { creature[which].going = OTTER_UP; creature[which].journey = OTTER_LEFT; } field[x][y] = OTTER; draw(x, y, OTTER); default: break; } creature[which].alive = TRUE; creature[which].x = x; creature[which].y = y; creature[which].deltax = deltax; creature[which].deltay = deltay; creature[which].species = species; } MODULE ULONG arand(ULONG number) { // Returns a value between 0 and number, inclusive. return((ULONG) (rand() % (number + 1))); } MODULE void protcreature(UBYTE player, UBYTE which) { UBYTE i; /* Handles collisions between protectors and creatures. */ switch(creature[which].species) { case BIRD: case CLOUD: case DOG: case DRIP: case ORB: case PENGUIN: effect(FXUSE_PROTECTOR); wormkillcreature(player, which); break; case FRAGMENT: effect(FXUSE_PROTECTOR); reflect(which); break; case MISSILE: if (player != creature[which].type) { effect(FXUSE_PROTECTOR); wormkillcreature(player, which); } else creature[which].visible = FALSE; break; case WHIRLWIND: for (i = 0; i <= PROTECTORS; i++) { if ( protector[player][i].alive && protector[player][i].x == creature[which].x && protector[player][i].y == creature[which].y ) { protector[player][i].alive = FALSE; } } break; default: break; } } MODULE void wormcreature(UBYTE player, UBYTE which) { /* Handles collisions between worms and creatures. */ switch(creature[which].species) { case CLOUD: wormkillcreature(player, which); if (worm[player].armour == 0) { worm[player].alive = FALSE; worm[player].cause = CLOUD; worm[player].victor = -1; } break; case DOG: if (creature[which].dormant == DORMANT) { effect(FXBORN_DOG); creature[which].dormant = AWAKENING; creature[which].type = player; worm[player].last = DOG; } else { wormkillcreature(player, which); if (worm[player].armour == 0) { worm[player].alive = FALSE; worm[player].cause = DOG; worm[player].victor = -1; } } break; case DRIP: wormkillcreature(player, which); if (player != creature[which].type && worm[player].armour == 0) { worm[player].alive = FALSE; worm[player].cause = FIRSTDRIP + creature[which].type; worm[player].victor = -1; } break; case FISH: case GOAT: case OCTOPUS: worm[player].alive = FALSE; worm[player].cause = creature[which].species; worm[player].victor = -1; break; case FRAGMENT: if (worm[player].armour > 0) { effect(FXUSE_ARMOUR); reflect(which); } else { wormkillcreature(player, which); worm[player].cause = FRAGMENT; worm[player].victor = -1; worm[player].alive = FALSE; } break; case MISSILE: if (creature[which].type == player) { creature[which].visible = FALSE; } else { wormkillcreature(player, which); if (worm[player].armour == 0) { worm[player].cause = FIRSTMISSILE + creature[which].type; worm[player].victor = creature[which].type; worm[player].alive = FALSE; } else effect(FXUSE_ARMOUR); } break; case BIRD: case PENGUIN: case ORB: wormkillcreature(player, which); if (worm[player].armour > 0) { effect(FXUSE_ARMOUR); } else { worm[player].cause = creature[which].species; worm[player].victor = -1; worm[player].alive = FALSE; } break; case WHIRLWIND: worm[player].cause = WHIRLWIND; worm[player].victor = -1; worm[player].alive = FALSE; break; default: break; } } MODULE void creaturecreature(UBYTE which1, UBYTE which2) { if (creature[which1].hardness > creature[which2].hardness) { creature[which2].alive = FALSE; if (creature[which1].species == MISSILE) { wormkillcreature(creature[which1].type, which2); } } elif (creature[which1].hardness < creature[which2].hardness) { creature[which1].alive = FALSE; if (creature[which2].species == MISSILE) { wormkillcreature(creature[which2].type, which1); } } else { creature[which1].alive = creature[which2].alive = FALSE; change(creature[which1].x, creature[which1].y, BONUS); } } /* NAME align -- right-justify a string within another string SYNOPSIS align(STRPTR, SBYTE, TEXT); FUNCTION Moves all text in a string to the right, padding with spaces. Does not itself add a null terminator. INPUTS string - pointer to the string of text size - size in characters of the containing string filler - what to pad the left of the string with NOTE Null terminators are written over by this function, but that does not matter, because calling functions use Text() with an explicit length. This function only works with monospaced fonts. */ AGLOBAL void align(STRPTR string, SBYTE size, TEXT filler) { SBYTE i, shift, length; length = strlen((const char*) string); shift = size - length; for (i = 1; i <= length; i++) *(string + size - i) = *(string + size - i - shift); for (i = 0; i <= shift - 1; i++) *(string + i) = filler; } MODULE void ReadGameports(void) { ReadStandardJoystick(0); ReadStandardJoystick(1); ReadAdapterJoystick(2); ReadAdapterJoystick(3); ReadGamepads(); } MODULE void drawmissile(SBYTE x, SBYTE y, UBYTE which) { draw(x, y, missileframes[creature[which].type][creature[which].frame]); } MODULE void checkrectangle(SBYTE direction, SBYTE player, SBYTE horizontalsize, SBYTE verticalsize) { AUTO SBYTE i, x, y, leftx, rightx, topy, bottomy; AUTO UBYTE c; PERSIST struct { SBYTE deltax[4], deltay[4]; } deltas[4] = { { { 0, -1, 0, 1}, // northwest {-1, 0, 1, 0} }, { { 0, 1, 0, -1}, // northeast {-1, 0, 1, 0} }, { { 0, 1, 0, -1}, // southeast { 1, 0, -1, 0} }, { { 0, -1, 0, 1}, // southwest { 1, 0, -1, 0} } }; PERSIST struct { SBYTE leftx, rightx, topy, bottomy; } enclose[4] = { {-127, -1, -127, -1 }, // northwest { 1, 127, -127, -1 }, // northeast { 1, 127, 1, 127 }, // southeast {-127, -1, 1, 127 } // southwest }; x = worm[player].x; y = worm[player].y; // for speed for (i = 0; i <= verticalsize; i++) { x += deltas[direction].deltax[0]; y += deltas[direction].deltay[0]; if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player) { return; } } for (i = 0; i <= horizontalsize; i++) { x += deltas[direction].deltax[1]; y += deltas[direction].deltay[1]; if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player) { return; } } for (i = 0; i <= verticalsize; i++) { x += deltas[direction].deltax[2]; y += deltas[direction].deltay[2]; if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player) { return; } } for (i = 0; i <= horizontalsize - 1; i++) { x += deltas[direction].deltax[3]; y += deltas[direction].deltay[3]; if ((!valid(x, y)) || field[x][y] != FIRSTTAIL + player) { return; } } effect(FXDO_ENCLOSE); enclosed = TRUE; if (enclose[direction].leftx < -1) enclose[direction].leftx = -horizontalsize; elif (enclose[direction].leftx > 1) enclose[direction].leftx = horizontalsize + 1; if (enclose[direction].rightx < -1) enclose[direction].rightx = -(horizontalsize + 1); elif (enclose[direction].rightx > 1) enclose[direction].rightx = horizontalsize; if (enclose[direction].topy < -1) enclose[direction].topy = -verticalsize; elif (enclose[direction].topy > 1) enclose[direction].topy = verticalsize + 1; if (enclose[direction].bottomy < -1) enclose[direction].bottomy = -(verticalsize + 1); elif (enclose[direction].bottomy > 1) enclose[direction].bottomy = verticalsize; leftx = worm[player].x + enclose[direction].leftx; rightx = worm[player].x + enclose[direction].rightx; topy = worm[player].y + enclose[direction].topy; bottomy = worm[player].y + enclose[direction].bottomy; // assert(leftx >= 0 && rightx <= FIELDX && topy >= 0 && bottomy <= FIELDY && leftx < rightx && topy < bottomy); for (x = leftx; x <= rightx; x++) { for (y = topy; y <= bottomy; y++) { c = field[x][y]; if ((c >= FIRSTEMPTY && c <= LASTEMPTY) || (c >= FIRSTTAIL && c <= LASTTAIL) || (c >= FIRSTGLOW && c <= LASTGLOW)) { change(x, y, FIRSTGLOW + player); wormscore(player, POINTS_ENCLOSURE + POINTS_TURNGOLD); } } } } MODULE void verynewlevel(void) { SBYTE advancer, player; UWORD counter[4] = {0, 0, 0, 0}; SBYTE x, y; for (player = 0; player <= 3; player++) { if (worm[player].lives) { for (x = 0; x <= FIELDX; x++) { for (y = 0; y <= FIELDY; y++) { if (field[x][y] == FIRSTTAIL + player || field[x][y] == FIRSTGLOW + player) { counter[player]++; } } } } } if (counter[0] >= counter[1] && counter[0] >= counter[2] && counter[0] >= counter[3]) { advancer = 0; } elif (counter[1] >= counter[0] && counter[1] >= counter[2] && counter[1] >= counter[3]) { advancer = 1; } elif (counter[2] >= counter[0] && counter[2] >= counter[1] && counter[2] >= counter[3]) { advancer = 2; } else { advancer = 3; } if (level++ == 0) { level = reallevel + 1; reallevel = 0; } stopfx(); if (level > levels) effect(FXCELEBRATE); newlevel(advancer); } MODULE void getnumber(SBYTE player) { worm[player].numbers++; wormscore(player, POINTS_LETTER); number++; if (number == 10) { verynewlevel(); } // The calling function is responsible for calling putnumber() if it // wants a new number to appear. }