/* Copyright (C) 2003 Parallel Realities Copyright (C) 2011, 2012, 2013 Guus Sliepen Copyright (C) 2012, 2015-2020 Layla Marchant This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "SDL.h" #include "defs.h" #include "structs.h" #include "alien.h" #include "audio.h" #include "engine.h" #include "colors.h" #include "game.h" #include "gfx.h" #include "info.h" #include "player.h" #include "renderer.h" #include "screen.h" #include "weapons.h" #include "window.h" Object player; int player_chargerFired = 0; int player_damageDelay = 0; int player_resetDamageDelay = 0; /* Initialises the player for a new game. */ void player_init() { player.active = 1; player.x = screen->w / 2; player.y = screen->h / 2; player.speed = 2; player.systemPower = player.maxShield; player.face = 0; player.image[0] = gfx_shipSprites[SS_FIREFLY]; player.image[1] = gfx_shipSprites[SS_FIREFLY_L]; player.engineX = player.image[0]->w; player.engineY = (player.image[0]->h / 2); player.owner = &player; player.flags = FL_FRIEND; player.weaponType[0] = W_PLAYER_WEAPON; if (weapons[W_PLAYER_WEAPON].ammo[0] < game.minPlasmaOutput) weapons[W_PLAYER_WEAPON].ammo[0] = game.minPlasmaOutput; if (weapons[W_PLAYER_WEAPON].damage < game.minPlasmaDamage) weapons[W_PLAYER_WEAPON].damage = game.minPlasmaDamage; if (weapons[W_PLAYER_WEAPON].reload[0] > rate2reload[game.minPlasmaRate]) weapons[W_PLAYER_WEAPON].reload[0] = rate2reload[game.minPlasmaRate]; player.hit = 0; engine.lowShield = (player.maxShield >= 3) ? (player.maxShield / 3) : 1; engine.averageShield = engine.lowShield + engine.lowShield; if ((player.weaponType[1] == W_CHARGER) || (player.weaponType[1] == W_LASER)) player.ammo[1] = 0; } void player_setTarget(int index) { engine.targetIndex = index; } /* Attempt to damage the player by ``amount``. If ``delay`` is specified, delay any damage dealt by that number of frames (i.e. require that number of frames of continuous damage before it registers). Return 1 if damage is inflicted, 0 otherwise. */ void player_damage(int amount, int delay) { int oldshield = player.shield; player_resetDamageDelay = 0; if ((!engine.cheatShield) && (engine.missionCompleteTimer == 0) && ((!player.hit) || (game.difficulty == DIFFICULTY_ORIGINAL) || ((player.shield != engine.lowShield) && (player.shield != 1)))) { if ((game.difficulty == DIFFICULTY_ORIGINAL) || (player_damageDelay >= delay)) { player.shield -= amount; LIMIT(player.shield, 0, player.maxShield); player.hit = 5; // Damage flash timer audio_playSound(SFX_HIT, player.x, player.y); // Damage tiers (not in Classic mode) if ((oldshield > engine.lowShield) && (player.shield <= engine.lowShield)) { info_setLine("!!! WARNING: SHIELD LOW !!!", FONT_RED); if (game.difficulty != DIFFICULTY_ORIGINAL) { player.shield = engine.lowShield; player_damageDelay = 0; } } else if ((oldshield > 1) && (player.shield <= 1)) { info_setLine("!!! WARNING: SHIELD CRITICAL !!!", FONT_RED); if (game.difficulty != DIFFICULTY_ORIGINAL) { player.shield = 1; player_damageDelay = 0; } } } else { player_damageDelay += amount; } } } void player_checkShockDamage(float x, float y) { float distX = fabsf(x - player.x); float distY = fabsf(y - player.y); // Don't let the player be hurt by an explosion after they have completed // all the mission Objectives. That would be *really* annoying! if ((engine.cheatShield) || (engine.missionCompleteTimer != 0)) return; if ((distX <= 50) && (distY <= 50)) { if (distX >= 1) distX /= 5; if (distY >= 1) distY /= 5; player_damage((int)((10 - distX) + (10 - distY)), 0); player.hit = 10; } } void player_exit() { player_chargerFired = 0; if ((player.weaponType[1] == W_CHARGER) || (player.weaponType[1] == W_LASER)) player.ammo[1] = 0; } void player_flushInput() { for (int i = 0; i < KEY_LAST; i++) engine.keyState[i] = 0; while (SDL_PollEvent(&engine.event)){} } static enum keys mapkey(int code) { switch (code) { case SDLK_UP: case SDLK_KP_8: return KEY_UP; case SDLK_DOWN: case SDLK_KP_2: case SDLK_KP_5: return KEY_DOWN; case SDLK_LEFT: case SDLK_KP_4: return KEY_LEFT; case SDLK_RIGHT: case SDLK_KP_6: return KEY_RIGHT; case SDLK_LCTRL: case SDLK_RCTRL: case SDLK_RETURN: case SDLK_z: case SDLK_y: case SDLK_c: case SDLK_a: case SDLK_d: case SDLK_f: case SDLK_SLASH: case SDLK_COMMA: case SDLK_1: case SDLK_3: case SDLK_KP_0: case SDLK_HOME: case SDLK_END: return KEY_FIRE; case SDLK_SPACE: case SDLK_x: case SDLK_s: case SDLK_PERIOD: case SDLK_2: case SDLK_KP_1: case SDLK_PAGEUP: case SDLK_PAGEDOWN: return KEY_ALTFIRE; case SDLK_LSHIFT: case SDLK_RSHIFT: case SDLK_LALT: case SDLK_RALT: case SDLK_KP_7: case SDLK_KP_9: return KEY_SWITCH; case SDLK_p: case SDLK_PAUSE: return KEY_PAUSE; case SDLK_ESCAPE: case SDLK_q: case SDLK_BACKSPACE: case SDLK_DELETE: return KEY_ESCAPE; case SDLK_F11: return KEY_FULLSCREEN; default: return KEY_DUMMY; } } void player_getInput() { static int prevjoyup = 0, prevjoydown = 0, prevjoyleft = 0, prevjoyright = 0; int joyup, joydown, joyleft, joyright; double val; static int px = -1, py = -1; int x, y, w, h; while (SDL_PollEvent(&engine.event)) { switch (engine.event.type) { case SDL_QUIT: exit(0); break; case SDL_MOUSEBUTTONDOWN: if (engine.gameSection == SECTION_INTERMISSION) { if (engine.event.button.button == SDL_BUTTON_LEFT) engine.keyState[KEY_FIRE] = 1; if (engine.event.button.button == SDL_BUTTON_RIGHT) engine.keyState[KEY_ALTFIRE] = 1; } break; case SDL_KEYDOWN: if (!engine.event.key.repeat) { engine.keyState[mapkey(engine.event.key.keysym.sym)] = 1; if (engine.gameSection != SECTION_GAME) engine.paused = 0; } break; case SDL_KEYUP: if (engine.event.key.keysym.sym != SDLK_p) engine.keyState[mapkey(engine.event.key.keysym.sym)] = 0; break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: switch (engine.event.jbutton.button) { case 0: case 3: engine.keyState[KEY_ALTFIRE] = CSDLP( engine.event.jbutton.state); break; case 1: case 2: engine.keyState[KEY_FIRE] = CSDLP( engine.event.jbutton.state); break; case 4: case 6: engine.keyState[KEY_ESCAPE] = CSDLP( engine.event.jbutton.state); break; case 5: case 7: case 8: engine.keyState[KEY_SWITCH] = CSDLP( engine.event.jbutton.state); break; case 9: engine.keyState[KEY_PAUSE] = CSDLP( engine.event.jbutton.state); break; } break; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: switch (engine.event.cbutton.button) { case SDL_CONTROLLER_BUTTON_A: case SDL_CONTROLLER_BUTTON_Y: engine.keyState[KEY_ALTFIRE] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_B: case SDL_CONTROLLER_BUTTON_X: engine.keyState[KEY_FIRE] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: engine.keyState[KEY_ESCAPE] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_BACK: case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: engine.keyState[KEY_SWITCH] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_START: engine.keyState[KEY_PAUSE] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_DPAD_UP: engine.keyState[KEY_UP] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: engine.keyState[KEY_DOWN] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: engine.keyState[KEY_LEFT] = CSDLP( engine.event.cbutton.state); break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: engine.keyState[KEY_RIGHT] = CSDLP( engine.event.cbutton.state); break; } break; case SDL_JOYHATMOTION: engine.keyState[KEY_UP] = (engine.event.jhat.value & SDL_HAT_UP ? 1 : 0); engine.keyState[KEY_DOWN] = (engine.event.jhat.value & SDL_HAT_DOWN ? 1 : 0); engine.keyState[KEY_LEFT] = (engine.event.jhat.value & SDL_HAT_LEFT ? 1 : 0); engine.keyState[KEY_RIGHT] = (engine.event.jhat.value & SDL_HAT_RIGHT ? 1 : 0); break; case SDL_JOYAXISMOTION: if (engine.gameSection == SECTION_TITLE) { if (engine.event.jaxis.axis == 0) { joyleft = engine.event.jaxis.value < -16384; joyright = engine.event.jaxis.value >= 16384; if (joyleft != prevjoyleft) engine.keyState[KEY_LEFT] = prevjoyleft = joyleft; if (joyright != prevjoyright) engine.keyState[KEY_RIGHT] = prevjoyright = joyright; } else if (engine.event.jaxis.axis == 1) { joyup = engine.event.jaxis.value < -16384; joydown = engine.event.jaxis.value >= 16384; if (joyup != prevjoyup) engine.keyState[KEY_UP] = prevjoyup = joyup; if (joydown != prevjoydown) engine.keyState[KEY_DOWN] = prevjoydown = joydown; } } else { val = MIN(1, (double)(abs(engine.event.jaxis.value)) / JS_MAX); if (val < JS_DEADZONE) val = 0; if (engine.event.jaxis.axis == 0) engine.xaxis = copysign(val, engine.event.jaxis.value); else if (engine.event.jaxis.axis == 1) engine.yaxis = copysign(val, engine.event.jaxis.value); } break; case SDL_CONTROLLERAXISMOTION: if (engine.gameSection == SECTION_TITLE) { if (engine.event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX) { joyleft = engine.event.caxis.value < -16384; joyright = engine.event.caxis.value >= 16384; if (joyleft != prevjoyleft) engine.keyState[KEY_LEFT] = prevjoyleft = joyleft; if (joyright != prevjoyright) engine.keyState[KEY_RIGHT] = prevjoyright = joyright; } else if (engine.event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) { joyup = engine.event.caxis.value < -16384; joydown = engine.event.caxis.value >= 16384; if (joyup != prevjoyup) engine.keyState[KEY_UP] = prevjoyup = joyup; if (joydown != prevjoydown) engine.keyState[KEY_DOWN] = prevjoydown = joydown; } } else { val = MIN(1, (double)(abs(engine.event.caxis.value)) / JS_MAX); if (val < JS_DEADZONE) val = 0; if (engine.event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX) engine.xaxis = copysign(val, engine.event.caxis.value); else if (engine.event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) engine.yaxis = copysign(val, engine.event.caxis.value); } break; case SDL_WINDOWEVENT: if (engine.event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { screen_adjustDimensions(engine.event.window.data1, engine.event.window.data2); renderer_reset(); gfx_scaleBackground(); screen_clear(black); renderer_update(); screen_clear(black); screen_addBuffer(0, 0, screen->w, screen->h); } if (engine.autoPause && (engine.event.window.event == SDL_WINDOWEVENT_FOCUS_LOST)) engine.paused = 1; break; } if (engine.keyState[KEY_FULLSCREEN]) { engine_setFullscreen(!engine.fullScreen); engine.keyState[KEY_FULLSCREEN] = 0; } } if (engine.gameSection == SECTION_INTERMISSION) { // Get the current mouse position SDL_GetMouseState(&x, &y); SDL_GetWindowSize(window, &w, &h); x = screen->w * x / w; y = screen->h * y / h; if (px == x && py == y) { if (engine.keyState[KEY_UP]) engine.cursor_y -= 4; if (engine.keyState[KEY_DOWN]) engine.cursor_y += 4; if (engine.keyState[KEY_LEFT]) engine.cursor_x -= 4; if (engine.keyState[KEY_RIGHT]) engine.cursor_x += 4; if (engine.xaxis) engine.cursor_x += 10 * engine.xaxis; if (engine.yaxis) engine.cursor_y += 10 * engine.yaxis; } else { engine.cursor_x = px = x; engine.cursor_y = py = y; } } } void player_leaveSector() { engine.keyState[KEY_UP] = 0; engine.keyState[KEY_DOWN] = 0; engine.keyState[KEY_LEFT] = 0; engine.keyState[KEY_RIGHT] = 0; engine.keyState[KEY_FIRE] = 0; engine.keyState[KEY_ALTFIRE] = 0; engine.xaxis = 0; engine.yaxis = 0; if (engine.done == ENGINE_RUNNING) engine.done = ENGINE_FORMATION; if (engine.done == ENGINE_FORMATION) { player.face = 0; if (player.x > -100) { player.x += engine.ssx; engine.ssx -= 1; if (player.y > screen->h / 2) player.y--; if (player.y < screen->h / 2) player.y++; } if (player.x <= -100) { engine.done = ENGINE_SYSEXIT; audio_playSound(SFX_FLY, screen->w / 2, screen->h / 2); } } if (engine.done == ENGINE_SYSEXIT) { player.face = 0; player.x += WARP_SPEED; engine.ssx -= 0.2; if (player.x > (2 * screen->w)) engine.done = ENGINE_CLOSING; } }