/** * @file player.c * * @brief Contains all the player related stuff. */ #include #include "lephisto.h" #include "pilot.h" #include "log.h" #include "opengl.h" #include "font.h" #include "ldata.h" #include "lxml.h" #include "space.h" #include "rng.h" #include "land.h" #include "sound.h" #include "economy.h" #include "pause.h" #include "menu.h" #include "toolkit.h" #include "dialogue.h" #include "mission.h" #include "llua_misn.h" #include "ltime.h" #include "hook.h" #include "map.h" #include "lfile.h" #include "spfx.h" #include "unidiff.h" #include "comm.h" #include "intro.h" #include "perlin.h" #include "ai.h" #include "music.h" #include "gui.h" #include "player.h" #define XML_START_ID "Start" /**< Module start xml document identifier. */ #define START_DATA "../dat/start.xml" /**< Module start information file. */ #define PLAYER_RESERVED_CHANNELS 6 /**< Number of channels to reserve for player sounds. */ #define PLAYER_ENGINE_CHANNEL 9 /**< Player channel for engine noises. */ #define PLAYER_GUI_CHANNEL 9 /**< Player channel. */ /* Player stuff. */ Pilot* player = NULL; /**< The player. */ static Ship* player_ship = NULL; /**< Temp ship to hold when naming it. */ static double player_px = 0.; /**< Temp X position. */ static double player_py = 0.; /**< Temp Y position. */ static double player_vx = 0.; /**< Temp X velocity. */ static double player_vy = 0.; /**< Temp Y velocity. */ static double player_dir = 0.; /**< Temp direction. */ static int player_credits = 0; /**< Temp hack on create. */ static char* player_mission = NULL; /**< More hack. */ int player_enemies = 0;; /**< Number of enemies player has in system. */ /* Licenses. */ static char** player_licenses = NULL; /**< Licenses player has. */ static int player_nlicenses = 0; /**< Number of licenses player has. */ /* * Player sounds. */ int snd_target = -1; /**< Sound when targetting. */ int snd_jump = -1; /**< Sound when can jump. */ int snd_nav = -1; /**< Sound when changing nav computer. */ /* Player pilot stack - Ships she owns. */ static Pilot** player_stack = NULL; /**< Stack of ships player has. */ static char** player_lstack = NULL; /**< Names of the planet the ships are at. */ static int player_nstack = 0; /**< Number of ships player has. */ /* Player global properties. */ char* player_name = NULL; /**< Player name. */ double player_crating = 0; /**< Player combar rating. */ unsigned int player_flags = 0; /**< Player flags. */ /* Input.c */ double player_left = 0.; /**< Player left turn velocity from input.c. */ double player_right = 0.; /**< Player right turn velocity from input.c */ static double player_acc = 0.; /**< Accel velocity from input. */ /* Internal */ int planet_target = -1; /**< Targetted planet. -1 is none. */ int hyperspace_target = -1; /**< Target hyperspace route. -1 is none. */ /* For death etc. */ static double player_timer = 0; /**< For death and such. */ static Vec2 player_cam; /**< Again, for death etc. */ static int* missions_done = NULL; /**< Saves position of completed missions. */ static int missions_mdone = 0; /**< Memory size of completed missions. */ static int missions_ndone = 0; /**< Number of completed missions. */ /* Extern stuff for player ships. */ extern Pilot** pilot_stack; extern int pilot_nstack; /* Map stuff for autonav. */ extern int map_npath; /* External. */ extern void pilot_render(const Pilot* pilot); /* Extern is in Pilot.* */ /* Internal. */ /* Creation. */ static int player_newMake(void); static void player_newShipMake(char* name); /* Sound. */ static void player_initSound(void); /* Save/Load. */ static int player_saveShip(xmlTextWriterPtr writer, Pilot* ship, char* loc); static int player_parse(xmlNodePtr parent); static int player_parseDone(xmlNodePtr parent); static int player_parseLicenses(xmlNodePtr parent); static int player_parseShip(xmlNodePtr parent, int is_player); /* Externed. */ void player_dead(void); void player_destroyed(void); int player_save(xmlTextWriterPtr writer); int player_load(xmlNodePtr parent); void player_think(Pilot* player); void player_brokeHyperspace(void); double player_faceHyperspace(void); /** * @brief Create a new player. * * - Cleans up after old player. * - Prompts for name. * * @sa player_newMake */ void player_new(void) { int r; /* Let's not seg fault due to a lack of environment. */ player_flags = 0; player_setFlag(PLAYER_CREATING); vectnull(&player_cam); gl_bindCamera(&player_cam); /* Set up GUI. */ gui_setDefaults(); /* Setup sound. */ player_initSound(); /* Cleanup player stuff if we'll be re-creating. */ player_cleanup(); var_cleanup(); missions_cleanup(); space_clearKnown(); land_cleanup(); diff_clear(); factions_reset(); map_close(); player_name = dialogue_input("Player Name", 3, 20, "Please tell me your name:"); /* Player cancelled dialogue. */ if(player_name == NULL) { menu_main(); return; } if(lfile_fileExists("%ssaves/%s.ls", lfile_basePath(), player_name)) { r = dialogue_YesNo("Overwrite", "You already have a pilot named %s. Overwrite?", player_name); if(r == 0) { /* Nupe. */ player_new(); return; } } if(player_newMake()) return; intro_display("../dat/intro", "intro"); /* Add the mission if found. */ if(player_mission != NULL) { if(mission_start(player_mission) < 0) WARN("Failed to run start mission '%s'.", player_mission); free(player_mission); player_mission = NULL; } } /** * @brief Actually create a new player. * @return 0 on success. */ static int player_newMake(void) { Ship* ship; char* sysname; uint32_t bufsize; char* buf; int l, h, tl, th; double x, y; /* Sane defaults. */ sysname = NULL; player_mission = NULL; buf = ldata_read(START_DATA, &bufsize); xmlNodePtr node, cur, tmp; xmlDocPtr doc = xmlParseMemory(buf, bufsize); node = doc->xmlChildrenNode; if(!xml_isNode(node, XML_START_ID)) { ERR("Malformed '"START_DATA"' file: missing root element '"XML_START_ID"'"); return -1; } node = node->xmlChildrenNode; /* First system node. */ if(node == NULL) { ERR("Malformed '"START_DATA"' file: does not contain elements"); return -1; } do { /* We are interested in the player. */ if(xml_isNode(node, "player")) { cur = node->children; do { if(xml_isNode(cur, "ship")) ship = ship_get(xml_get(cur)); else if(xml_isNode(cur, "credits")) { /* MONIEZZ!!!! -- We call these SCred AKA SaraCred. Lame right? */ tmp = cur->children; do { xmlr_int(tmp, "low", l); xmlr_int(tmp, "high", h); } while(xml_nextNode(tmp)); } else if(xml_isNode(cur, "system")) { tmp = cur->children; do { /* System name. @todo Chance based on percentage. */ xmlr_strd(tmp, "name", sysname); /* Position. */ xmlr_float(tmp, "x", x); xmlr_float(tmp, "y", y); } while(xml_nextNode(tmp)); } xmlr_float(cur, "player_crating", player_crating); if(xml_isNode(cur, "date")) { tmp = cur->children; do { xmlr_int(tmp, "low", tl); xmlr_int(tmp, "high", th); } while(xml_nextNode(tmp)); } /* Check for mission. */ if(xml_isNode(cur, "mission")) { if(player_mission != NULL) { WARN("start.xml already contains a mission node!"); continue; } player_mission = xml_getStrd(cur); } } while((cur = cur->next)); } }while((node = node->next)); /* Clean up. */ xmlFreeDoc(doc); free(buf); /* Money. */ player_credits = RNG(l, h); /* Time. */ ltime_set(RNG(tl*1000*LTIME_UNIT_LENGTH, th*1000*LTIME_UNIT_LENGTH)); /* Welcome message. */ player_message("Welcome to "APPNAME"!"); player_message("v%d.%d.%d", VMAJOR, VMINOR, VREV); /* Try to create the pilot, if fails re-ask for player name. */ if(player_newShip(ship, x, y, 0., 0., RNG(0, 369)/180.*M_PI) != 0) { player_new(); return -1; } space_init(sysname); free(sysname); /* Clear the map. */ map_clear(); /* Start the economy. */ economy_init(); return 0; } /** * @brief Create a new ship for player. * @return 0 indicates success, -1 means dialogue was cancelled. * * @sa player_newShipMake */ int player_newShip(Ship* ship, double px, double py, double vx, double vy, double dir) { char* ship_name; /* Temp values while player doesn't exist. */ player_ship = ship; player_px = px; player_py = py; player_vx = vx; player_vy = vy; player_dir = dir; ship_name = dialogue_input("Ship Name", 3, 20, "Please name your shiny new %s", ship->name); /* Dialogue cancelled. */ if(ship_name == NULL) return -1; player_newShipMake(ship_name); free(ship_name); return 0; } /** * @brief Actually create the new ship. */ static void player_newShipMake(char* name) { Vec2 vp, vv; /* Store the current ship if it exists. */ if(player != NULL) { player_stack = realloc(player_stack, sizeof(Pilot*)*(player_nstack+1)); player_stack[player_nstack] = pilot_copy(player); player_lstack = realloc(player_lstack, sizeof(char*)*(player_nstack+1)); player_lstack[player_nstack] = strdup(land_planet->name); player_nstack++; if(!player_credits) player_credits = player->credits; pilot_destroy(player); } /* In case we're respawning. */ player_rmFlag(PLAYER_CREATING); /* Hackish position setting. */ vect_cset(&vp, player_px, player_py); vect_cset(&vv, player_vx, player_vy); /* Create the player. */ pilot_create(player_ship, name, faction_get("Player"), NULL, player_dir, &vp, &vv, PILOT_PLAYER); gl_bindCamera(&player->solid->pos); /* Set opengl camera. */ /* Copy cargo over. */ if(player_nstack > 0) { /* Not during creation though. */ pilot_moveCargo(player, player_stack[player_nstack-1]); /* Recalculate stats after cargo movement. */ pilot_calcStats(player); pilot_calcStats(player_stack[player_nstack-1]); } /* Moniez!! */ player->credits = player_credits; player_credits = 0; } /** * @brief Swap players current ship with her ship named 'shipname'. * @param shipname Ship to change to. */ void player_swapShip(char* shipname) { int i, j; Pilot* ship; for(i = 0; i < player_nstack; i++) { if(strcmp(shipname, player_stack[i]->name)==0) { /* Swap player and ship. */ ship = player_stack[i]; /* Move credits over. */ ship->credits = player->credits; /* Move cargo over. */ pilot_moveCargo(ship, player); /* Extra pass to calculate stats. */ pilot_calcStats(ship); pilot_calcStats(player); /* Now swap the players. */ player_stack[i] = player; for(j = 0; j < pilot_nstack; j++) /* Find pilot in stack to swap. */ if(pilot_stack[j] == player) { player = ship; pilot_stack[j] = ship; break; } gl_bindCamera(&player->solid->pos); /* Let's not forget the camera. */ return; } } WARN("Unable to swap player with ship '%s': Ship does not exist!", shipname); } /** * @brief Calculate the price of one of the players ships. * @param shipname Name of the ship. * @return The price of the ship in credits. */ int player_shipPrice(char* shipname) { int i; int price; Pilot* ship; /* Find ship. */ for(i = 0; i < player_nstack; i++) { if(strcmp(shipname, player_stack[i]->name)==0) { ship = player_stack[i]; /* Ship price is base price + outfit prices. */ price = ship_basePrice(ship->ship); for(i = 0; i < ship->noutfits; i++) price += ship->outfits[i].quantity * ship->outfits[i].outfit->price; return price; } } WARN("Unable to find price for players ship '%s': ship does not exist!", shipname); return -1; } /** * @brief Remove one of the players ships. * @param shipname Name of the ship to remove. */ void player_rmShip(char* shipname) { int i; for(i = 0; i < player_nstack; i++) { if(strcmp(shipname, player_stack[i]->name)==0) { /* Free player ship and location. */ pilot_free(player_stack[i]); free(player_lstack[i]); /* Move memory to make adjacent. */ memmove(player_stack+i, player_stack+i+1, sizeof(Pilot*) * (player_nstack-i-1)); memmove(player_lstack+i, player_lstack+i+1, sizeof(char*) * (player_nstack-i-1)); player_nstack--; /* Shrink stack. */ /* Realloc memory to smaller size. */ player_stack = realloc(player_stack, sizeof(Pilot*) * (player_nstack)); player_lstack = realloc(player_lstack, sizeof(char*) * (player_nstack)); } } } /** * @brief void player_cleanup(void * * @brief Clean up player stuff like player_stack. */ void player_cleanup(void) { int i; player_clear(); /* Clean up name. */ if(player_name != NULL) free(player_name); /* Clean up GUI. */ gui_cleanup(); /* Clean up the stack. */ if(player_nstack > 0) { for(i = 0; i < player_nstack; i++) { pilot_free(player_stack[i]); free(player_lstack[i]); } free(player_stack); player_stack = NULL; free(player_lstack); player_lstack = NULL; /* Nothing left. */ player_nstack = 0; } /* Clean up missions. */ if(missions_ndone > 0) { free(missions_done); missions_done = NULL; missions_ndone = 0; missions_mdone = 0; } /* Clean up licenses. */ if(player_nlicenses > 0) { for(i = 0; i < player_nlicenses; i++) free(player_licenses[i]); free(player_licenses); player_licenses = NULL; player_nlicenses = 0; } pilots_cleanAll(); if(player != NULL) { pilot_free(player); player = NULL; } } static int player_soundReserved = 0; /**< Has the player already reserved sound? */ /** * @brief Initialize the player sounds. */ static void player_initSound(void) { if(player_soundReserved) return; /* Allocated channels. */ sound_reserve(PLAYER_RESERVED_CHANNELS); sound_createGroup(PLAYER_ENGINE_CHANNEL, 0, 1); /* Channel for engine noises. */ sound_createGroup(PLAYER_GUI_CHANNEL, 1, PLAYER_RESERVED_CHANNELS-1); player_soundReserved = 1; /* Get sounds. */ snd_target = sound_get("target"); snd_jump = sound_get("jump"); snd_nav = sound_get("nav"); } /** * @brief Play a sound at the player. * @param sound ID of the sound to play. * @param once Play only once? */ void player_playSound(int sound, int once) { sound_playGroup(PLAYER_GUI_CHANNEL, sound, once); } /** * @brief Stop playing player sounds. */ void player_stopSound(void) { sound_stopGroup(PLAYER_GUI_CHANNEL); sound_stopGroup(PLAYER_ENGINE_CHANNEL); } /** * @brief Warp the player to the new position. * @param x X value of the position to warp to. * @param y Y value of the position to warp to. */ void player_warp(const double x, const double y) { vect_cset(&player->solid->pos, x, y); } /** * @brief Clear the targets. */ void player_clear(void) { if(player != NULL) player->target = PLAYER_ID; planet_target = -1; hyperspace_target = -1; } static char* player_ratings[] = { "None", "Smallfry", "Minor", "Average", "Major", "Fearsome", "Godlike" }; /**< Combat ratings. */ /** * @brief Get the players combat rating in a human readable string. * @return The players combat rating in a human readable string. */ const char* player_rating(void) { if(player_crating == 0.) return player_ratings[0]; else if(player_crating < 50.) return player_ratings[1]; else if(player_crating < 200.) return player_ratings[2]; else if(player_crating < 500.) return player_ratings[3]; else if(player_crating < 1000.) return player_ratings[4]; else if(player_crating < 2500.) return player_ratings[5]; else if(player_crating < 10000.) return player_ratings[6]; else return player_ratings[7]; } /** * @brief Get how many of the outfits the player owns. * @param outfitname Outfit to check how many the player owns. * @return The number of outfits matching outfitname owned. */ int player_outfitOwned(const char* outfitname) { int i; for(i = 0; i < player->noutfits; i++) if(strcmp(outfitname, player->outfits[i].outfit->name)==0) return player->outfits[i].quantity; return 0; } /** * @brief Get how many of the commodity the player has. * @param commodityname Commodity to check how many the player has. * @return The number of commodities owned matching commodityname. */ int player_cargoOwned(const char* commodityname) { int i; for(i = 0; i < player->ncommodities; i++) if(!player->commodities[i].id && strcmp(commodityname, player->commodities[i].commodity->name)==0) return player->commodities[i].quantity; return 0; } /** * @brief Render the player. */ void player_render(double dt) { /* Check to see if the death menu should pop up. */ if(player_isFlag(PLAYER_DESTROYED)) { player_timer -= dt; if(!toolkit && !player_isFlag(PLAYER_CREATING) && (player_timer < 0.)) { menu_death(); } } /* Render the player. */ if((player != NULL) && !player_isFlag(PLAYER_CREATING)) { /* Render players target first. */ gui_renderTarget(dt); /* Player is ontop of targeting graphic. */ pilot_render(player); } } /** * @brief Start autonav. */ void player_startAutonav(void) { if(hyperspace_target == -1) return; player_message("Autonav initialized"); player_setFlag(PLAYER_AUTONAV); } /** * @brief Aborts autonav */ void player_abortAutonav(char* reason) { if((player != NULL) && pilot_isFlag(player, PILOT_HYPERSPACE)) return; if(player_isFlag(PLAYER_AUTONAV)) { if(reason != NULL) player_message("Autonav aborting: %s!", reason); else player_message("Autonav aborted!"); player_rmFlag(PLAYER_AUTONAV); /* Get rid of acceleration. */ player_accelOver(); /* Drop out of possible different speed modes. */ if(dt_mod != 1.) pause_setSpeed(1.); /* Break possible hyperspacing. */ if(pilot_isFlag(player, PILOT_HYP_PREP)) { pilot_hyperspaceAbort(player); player_message("Aborting hyperspace sequence."); } } } /** * @brief Basically uses keyboard input instead of AI input. Used in pilot.c. * @param pplayer Player to think. */ void player_think(Pilot* pplayer) { Pilot* target; double d; double turn; /* Last I checked, the dead didn't think.. */ if(pilot_isFlag(pplayer, PILOT_DEAD)) { /* No point in accelerating or turning. */ pplayer->solid->dir_vel = 0.; vect_pset(&player->solid->force, 0., 0.); return; } /* Autonav takes over normal controls. */ if(player_isFlag(PLAYER_AUTONAV)) { /* Abort if lockons detected. */ if(pplayer->lockons > 0) player_abortAutonav("Missile Lockon Detected"); /* Try to jump. */ if(space_canHyperspace(pplayer)) player_jump(); else { /* Keep on moving. */ /* Only accelerate if facing move dir. */ d = pilot_face(pplayer, VANGLE(pplayer->solid->pos)); if((player_acc < 1.) && (d < MIN_DIR_ERR)) player_accel(1.); } } /* PLAYER_FACE will take over navigation. */ else if(player_isFlag(PLAYER_FACE)) { /* Try to face pilot target */ if(player->target != PLAYER_ID) { target = pilot_get(player->target); if(target != NULL) pilot_face(pplayer, vect_angle(&player->solid->pos, &target->solid->pos)); } /* If not try to face planet target. */ else if(planet_target != -1) pilot_face(pplayer, vect_angle(&player->solid->pos, &cur_system->planets[planet_target]->pos)); } /* PLAYER_REVERSE will take over navigation. */ else if(player_isFlag(PLAYER_REVERSE)) { /* Check to see if already stopped. */ #if 0 if(VMOD(pplayer->solid->vel) < MIN_VEL_ERR) player_accel(0.); else { d = pilot_face(pplayer, VANGLE(player->solid->vel) + M_PI); if((player_acc < 1.) && (d < MAX_DIR_ERR)) player_accel(1.); } #endif /* * Hm, I don't think automatic braking is amy good. */ pilot_face(pplayer, VANGLE(player->solid->vel) + M_PI); } /* Normal turning sheme. */ else { pplayer->solid->dir_vel = 0.; turn = 0; if(player_isFlag(PLAYER_TURN_LEFT)) turn -= player_left; if(player_isFlag(PLAYER_TURN_RIGHT)) turn += player_right; pplayer->solid->dir_vel -= pplayer->turn * turn; } /* Weapon shooting stuff. */ /* Primary weapon. */ if(player_isFlag(PLAYER_PRIMARY)) { pilot_shoot(pplayer, 0); player_setFlag(PLAYER_PRIMARY_L); } else if(player_isFlag(PLAYER_PRIMARY_L)) { pilot_shootStop(pplayer, 0); player_rmFlag(PLAYER_PRIMARY_L); } /* Secondary. */ if(player_isFlag(PLAYER_SECONDARY)) { /* Double tap stops beams. */ if(!player_isFlag(PLAYER_SECONDARY_L) && (pplayer->secondary != NULL) && outfit_isBeam(pplayer->secondary->outfit)) { pilot_shootStop(pplayer, 1); } else pilot_shoot(pplayer, 1); player_setFlag(PLAYER_SECONDARY_L); } else if(player_isFlag(PLAYER_SECONDARY_L)) { player_rmFlag(PLAYER_SECONDARY_L); } /* Afterburn! */ if(player_isFlag(PLAYER_AFTERBURNER)) { if(pilot_isFlag(player, PILOT_AFTERBURNER)) vect_pset(&pplayer->solid->force, pplayer->thrust * pplayer->afterburner->outfit->u.afb.thrust_perc + pplayer->afterburner->outfit->u.afb.thrust_abs, pplayer->solid->dir); else /* Ran out of energy. */ player_afterburnOver(); } else vect_pset(&pplayer->solid->force, pplayer->thrust * player_acc, pplayer->solid->dir); /* Sound. */ sound_updateListener(pplayer->solid->dir, pplayer->solid->pos.x, pplayer->solid->pos.y); } /* * For use in keybindings. */ /** * @brief Get the next secondary weapon. */ void player_secondaryNext(void) { int i; /* Get the current secondary weapon pos. */ if(player->secondary != NULL) i = player->secondary - player->outfits + 1; else i = 0; /* Get the next secondary weapon. */ for(; i < player->noutfits; i++) if(outfit_isProp(player->outfits[i].outfit, OUTFIT_PROP_WEAP_SECONDARY)) { pilot_switchSecondary(player, i); break; } /* We didn't find an outfit. */ if(i >= player->noutfits) pilot_switchSecondary(player, -1); /* Set ammo. */ pilot_setAmmo(player); } /** * @brief Get the previous secondary weapon. */ void player_secondaryPrev(void) { int i; /* Get current secondary weapon pos. */ if(player->secondary != NULL) i = player->secondary - player->outfits - 1; else i = player->noutfits - 1; /* Get next secondary weapon. */ for(; i >= 0; i--) if(outfit_isProp(player->outfits[i].outfit, OUTFIT_PROP_WEAP_SECONDARY)) { pilot_switchSecondary(player, i); break; } /* Found no bigger outfit. */ if(i < 0) pilot_switchSecondary(player, -1); /* Set ammo. */ pilot_setAmmo(player); } /** * @brief Cycle through planet targets. */ void player_targetPlanet(void) { hyperspace_target = -1; player_rmFlag(PLAYER_LANDACK); /* No target. */ if((planet_target == -1) && (cur_system->nplanets > 0)) { planet_target = 0; player_playSound(snd_nav, 1); return; } planet_target++; if(planet_target >= cur_system->nplanets) /* Last system. */ planet_target = -1; else player_playSound(snd_nav, 1); } /** * fn void player_land(void) * * @brief Try to land or target closest planet if no land target. */ void player_land(void) { int i; int tp; double td, d; Planet* planet; if(landed) { /* Player is already landed. */ takeoff(); return; } /* Check if there are planets to land on. */ if(cur_system->nplanets == 0) { player_message("There are no planets to land on."); return; } if(planet_target >= 0) { planet = cur_system->planets[planet_target]; if(!planet_hasService(planet, PLANET_SERVICE_LAND)) { player_message("You can't land here."); return; } else if(!player_isFlag(PLAYER_LANDACK)) { /* No landing authorization. */ if(planet_hasService(planet, PLANET_SERVICE_BASIC)) { /* Basic services. */ if(!areEnemies(player->faction, planet->faction)) { /* Friendly. */ player_message("%s> Permission to land granted.", planet->name); player_setFlag(PLAYER_LANDACK); } else /* Hostile. */ player_message("%s> Land request denied.", planet->name); } else { /* No shoes, no shirt, no lifeforms, no services. */ player_message("Ready to land on %s.", planet->name); player_setFlag(PLAYER_LANDACK); } return; } else if(vect_dist(&player->solid->pos, &planet->pos) > planet->gfx_space->sw) { player_message("You are too far away to land on %s.", planet->name); return; } else if((pow2(VX(player->solid->vel)) + pow2(VY(player->solid->vel))) > (double)pow2(MAX_HYPERSPACE_VEL)) { player_message("You are going too fast to land on %s.", planet->name); return; } /* Stop afterburning. */ player_afterburnOver(); /* Stop accelerating. */ player_accelOver(); /* Open land menu. */ player_soundPause(); land(planet); player_soundResume(); } else { /* Get nearest planet target. */ if(cur_system->nplanets == 0) { player_message("There are no planets to land on."); return; } td = -1; /* Temp distance. */ tp = -1; /* Temp planet. */ for(i = 0; i < cur_system->nplanets; i++) { planet = cur_system->planets[i]; d = vect_dist(&player->solid->pos, &planet->pos); if(planet_hasService(planet, PLANET_SERVICE_LAND) && ((tp == -1) || ((td == -1) || (td > d)))) { tp = i; td = d; } } planet_target = tp; player_rmFlag(PLAYER_LANDACK); /* No landable planet. */ if(planet_target < 0) return; player_land(); /* Re-run land protocol. */ } } /** * @brief Get a hyperspace target. */ void player_targetHyperspace(void) { planet_target = -1; /* Remove planet target. */ player_rmFlag(PLAYER_LANDACK); /* Get rid of landing permission. */ hyperspace_target++; map_clear(); /* Clear the current map path. */ if(hyperspace_target >= cur_system->njumps) hyperspace_target = -1; else player_playSound(snd_nav, 1); /* Map gets special treatment if open. */ if(map_isOpen()) { if(hyperspace_target == -1) map_select(NULL); else map_select(system_getIndex(cur_system->jumps[hyperspace_target])); } } /** * @brief Actually attempt to jump in hyperspace. */ void player_jump(void) { int i; /* Must have a jump target and not be already jumping. */ if((hyperspace_target == -1) || pilot_isFlag(player, PILOT_HYPERSPACE)) return; /* Already jumping, so we break jump. */ if(pilot_isFlag(player, PILOT_HYP_PREP)) { pilot_hyperspaceAbort(player); player_message("Aborting hyperspace sequence."); return; } i = space_hyperspace(player); if(i == -1) player_message("You are too close to gravity centers to initiate hyperspace."); else if(i == -2) player_message("You are moving too fast to enter hyperspace."); else if(i == -3) player_message("You do not have enough fuel to hyperspace jump."); else { player_message("Preparing for hyperspace."); /* Stop acceleration noise. */ player_accelOver(); /* Stop possible shooting. */ pilot_shootStop(player, 0); pilot_shootStop(player, 1); } } /** * @brief Player actually broke hyperspace (entering new system). */ void player_brokeHyperspace(void) { unsigned int tl, th; double d; /* Calculate the time it takes, call before space_init. */ tl = (unsigned int) floor(sqrt((double)player->solid->mass)/5.); th = (unsigned int) ceil(sqrt((double)player->solid->mass)/5.); tl *= LTIME_UNIT_LENGTH; th *= LTIME_UNIT_LENGTH; ltime_inc(RNG(tl, th)); /* Enter the new system. */ space_init(system_getIndex(cur_system->jumps[hyperspace_target])->name); /* Set position, pilot_update will handle the lowering of velocity. */ d = RNGF()*(HYPERSPACE_ENTER_MAX-HYPERSPACE_ENTER_MIN) + HYPERSPACE_ENTER_MIN; player_warp(-cos(player->solid->dir) * d, -sin(player->solid->dir) * d); /* Reduce fuel. */ player->fuel -= HYPERSPACE_FUEL; /* Stop hyperspace. */ pilot_rmFlag(player, PILOT_HYPERSPACE | PILOT_HYP_BEGIN | PILOT_HYP_PREP); /* Update the map. */ map_jump(); /* Disable autonavigation if arrived. */ if(player_isFlag(PLAYER_AUTONAV)) { if(hyperspace_target == -1) { player_message("Autonav arrived at destination."); player_rmFlag(PLAYER_AUTONAV); } else { player_message("Autonav continuing until destination (%d jump%s left).", map_npath, (map_npath==1) ? "" : "s"); } } /* Run the jump hooks. */ hooks_run("enter"); player_message("BANG!"); } /** * @brief Make player face her hyperspace target. * @return direction to face. */ double player_faceHyperspace(void) { double a; StarSystem* sys; sys = system_getIndex(cur_system->jumps[hyperspace_target]); a = ANGLE(sys->pos.x - cur_system->pos.x, sys->pos.y - cur_system->pos.y); return pilot_face(player, a); } /** * @brief Activate the afterburner. */ void player_afterburn(void) { if(pilot_isFlag(player, PILOT_HYP_PREP) || pilot_isFlag(player, PILOT_HYPERSPACE)) return; /* @todo Fancy effects. */ if((player != NULL) && (player->afterburner != NULL)) { player_setFlag(PLAYER_AFTERBURNER); pilot_setFlag(player, PILOT_AFTERBURNER); spfx_shake(player->afterburner->outfit->u.afb.rumble * SHAKE_MAX); sound_stopGroup(PLAYER_ENGINE_CHANNEL); sound_playGroup(PLAYER_ENGINE_CHANNEL, player->afterburner->outfit->u.afb.sound, 0); if(toolkit || paused) player_soundPause(); } } /** * @brief Deactivate the afterburner. */ void player_afterburnOver(void) { if((player != NULL) && (player->afterburner != NULL)) { player_rmFlag(PLAYER_AFTERBURNER); pilot_rmFlag(player, PILOT_AFTERBURNER); sound_stopGroup(PLAYER_ENGINE_CHANNEL); } } /** * @brief Start accelerating. * @param acc How much thrust should be applied of maximum (0 - 1). */ void player_accel(double acc) { if((player == NULL) || pilot_isFlag(player, PILOT_HYP_PREP) || pilot_isFlag(player, PILOT_HYPERSPACE)) return; player_acc = acc; sound_stopGroup(PLAYER_ENGINE_CHANNEL); sound_playGroup(PLAYER_ENGINE_CHANNEL, player->ship->sound, 0); if(toolkit || paused) player_soundPause(); } /** * @brief Done accelerating. */ void player_accelOver(void) { player_acc = 0; sound_stopGroup(PLAYER_ENGINE_CHANNEL); } /** * @brief Pause the ship's sounds. * * @todo Not use hardcoded PLAYER_ENGINE_CHANNEL sound... Ideally add support * for pausing/resuming groups in SDL_Mixer. */ void player_soundPause(void) { sound_pauseChannel(0); } /** * @brief Resumes the ships sounds. * * @todo Not use hardcoded PLAYER_ENGINE_CHANNEL sound... Ideally add support * for pausing/resuming groups in SDL_Mixer. */ void player_soundResume(void) { sound_resumeChannel(0); } /** * @brief Target the nearest hostile enemy to the player. */ void player_targetHostile(void) { unsigned int tp; int i; double d, td; tp = PLAYER_ID; d = 0; for(i = 0; i < pilot_nstack; i++) { /* Don't get if bribed. */ if(pilot_isFlag(pilot_stack[i], PILOT_BRIBED)) continue; /* Normbal unbribed. */ if(pilot_isFlag(pilot_stack[i], PILOT_HOSTILE) || areEnemies(FACTION_PLAYER, pilot_stack[i]->faction)) { td = vect_dist(&pilot_stack[i]->solid->pos, &player->solid->pos); if(!pilot_isDisabled(pilot_stack[i]) && ((tp == PLAYER_ID) || (td < d))) { d = td; tp = pilot_stack[i]->id; } } } if((tp != PLAYER_ID) && (tp != player->target)) player_playSound(snd_target, 1); player->target = tp; } /** * @brief Cycles to next target. */ void player_targetNext(void) { player->target = pilot_getNextID(player->target); if(player->target != PLAYER_ID) player_playSound(snd_target, 1); } void player_targetPrev(void) { player->target = pilot_getPrevID(player->target); if(player->target != PLAYER_ID) player_playSound(snd_target, 1); } /** * @brief Target the pilot. * @param prev 1 if is cycling backwords. */ void player_targetEscort(int prev) { int i; /* Check if current target is an escort. */ for(i = 0; i < player->nescorts; i++) { if(player->target == player->escorts[i]) { /* Cycle targets. */ if(prev) player->target = (i > 0) ? player->escorts[i-1] : PLAYER_ID; else player->target = (i < player->nescorts-1) ? player->escorts[i+1] : PLAYER_ID; break; } } /* Not found in loop. */ if(i > player->nescorts) { /* check see if she actually has escorts. */ if(player->nescorts > 0) { /* Cycle forward or backwords. */ if(prev) player->target = player->escorts[player->nescorts-1]; else player->target = player->escorts[0]; } else player->target = PLAYER_ID; } if(player->target != PLAYER_ID) player_playSound(snd_target, 1); } /** * @brief Player targets nearest pilot. */ void player_targetNearest(void) { unsigned int t; t = player->target; player->target = pilot_getNearestPilot(player); if((player->target != PLAYER_ID) && (t != player->target)) player_playSound(snd_target, 1); } static int screenshot_cur = 0; /**< Current screenshot at. */ /** * @brief Take a screenshot. */ void player_screenshot(void) { FILE* fp; int done; char filename[PATH_MAX]; if(lfile_dirMakeExist("%sscreenshots", lfile_basePath())) { WARN("Aborting screenshots"); return; } done = 0; do { if(screenshot_cur >= 999) { /* Just incase I fucked up. :) */ WARN("You have reached the maximum amount of screenshots [999]"); return; } snprintf(filename, PATH_MAX, "%sscreenshots/screenshot%03d.png", lfile_basePath(), screenshot_cur); fp = fopen(filename, "r"); /* Myeah, I know it's a horrible way to check. */ if(fp == NULL) done = 1; else { /* Next. */ screenshot_cur++; fclose(fp); } fp = NULL; } while(!done); /* Now gief me that screenshot. */ DEBUG("Taking screenshot [%03d]..", screenshot_cur); gl_screenshot(filename); } /** * @brief Open communication with the players target. */ void player_hail(void) { if(player->target != player->id) comm_open(player->target); } /** * @brief Player got killed. */ void player_dead(void) { gui_xoff = 0.; gui_yoff = 0.; } /** * @brief Player blew up in a nice fireball. */ void player_destroyed(void) { if(player_isFlag(PLAYER_DESTROYED)) return; vectcpy(&player_cam, &player->solid->pos); gl_bindCamera(&player_cam); player_setFlag(PLAYER_DESTROYED); player_timer = 5.; } /** * @brief Return a buffer with all the players ship names * or "None" if there are no ships. * * @param sships Fills sships with player_nships ship names. * @param tships Fills sships with player_nships ship target textures. */ void player_ships(char** sships, glTexture** tships) { int i; if(player_nstack==0) { sships[0] = strdup("None"); tships[0] = NULL; } else { for(i = 0; i < player_nstack; i++) { sships[i] = strdup(player_stack[i]->name); tships[i] = player_stack[i]->ship->gfx_target; } } } /** * @brief Get the ammount of ships player has in storage. * @return The number of ships the player has. */ int player_nships(void) { return player_nstack; } /* Return a specific ship. */ Pilot* player_getShip(char* shipname) { int i; for(i = 0; i < player_nstack; i++) if(strcmp(player_stack[i]->name, shipname)==0) return player_stack[i]; WARN("Player ship '%s' not found in stack", shipname); return NULL; } /* Return location of a specific ship. */ char* player_getLoc(char* shipname) { int i; for(i = 0; i < player_nstack; i++) if(strcmp(player_stack[i]->name, shipname)==0) return player_lstack[i]; WARN("Player ship '%s' not found in stack", shipname); return NULL; } void player_setLoc(char* shipname, char* loc) { int i; for(i = 0; i < player_nstack; i++) { if(strcmp(player_stack[i]->name, shipname)==0) { free(player_lstack[i]); player_lstack[i] = strdup(loc); return; } } WARN("Player ship '%s' not found in stack", shipname); } /* Marks a mission as completed. */ void player_missionFinished(int id) { missions_ndone++; if(missions_ndone > missions_mdone) { /* Need to grow. */ missions_mdone += 25; missions_done = realloc(missions_done, sizeof(int) * missions_mdone); } missions_done[missions_ndone-1] = id; } /* has player already completed a mission? */ int player_missionAlreadyDone(int id) { int i; for(i = 0; i < missions_ndone; i++) if(missions_done[i] == id) return 1; return 0; } /** * @brief Check to see if player has license. * @param license License to check to see if the player has. * @return 1 if has license, 0 if doesn't. */ int player_hasLicense(char* license) { int i; for(i = 0; i < player_nlicenses; i++) if(strcmp(license, player_licenses[i])==0) return 1; return 0; } /** * @brief Give the player a license. * @brief license License to give the player. */ void player_addLicense(char* license) { /* Player already has license. */ if(player_hasLicense(license)) return; /* Add the license. */ player_nlicenses++; player_licenses = realloc(player_licenses, sizeof(char*)*player_nlicenses); player_licenses[player_nlicenses-1] = strdup(license); } /** * @brief Get the players licenses. * @param nlicenses Amount of licenses the player has. * @return Name of the licenses she has. */ char** player_getLicenses(int* nlicenses) { *nlicenses = player_nlicenses; return player_licenses; } /* Save the player in a freaking xmlfile. */ int player_save(xmlTextWriterPtr writer) { int i; MissionData* m; xmlw_startElem(writer, "player"); /* Standard player details. */ xmlw_attr(writer, "name", player_name); xmlw_elem(writer, "rating", "%f", player_crating); xmlw_elem(writer, "credits", "%d", player->credits); xmlw_elem(writer, "time", "%d", ltime_get()); /* Current ship. */ xmlw_elem(writer, "location", land_planet->name); player_saveShip(writer, player, NULL); /* Current ship. */ /* Ships. */ xmlw_startElem(writer, "ships"); for(i = 0; i < player_nstack; i++) player_saveShip(writer, player_stack[i], player_lstack[i]); xmlw_endElem(writer); /* Player. */ /* Licenses. */ xmlw_startElem(writer, "licenses"); for(i = 0; i < player_nlicenses; i++) xmlw_elem(writer, "license", player_licenses[i]); xmlw_endElem(writer) /* "license" */ /* Mission the player has done. */ xmlw_startElem(writer, "missions_done"); for(i = 0; i < missions_ndone; i++) { m = mission_get(missions_done[i]); if(m != NULL) /* In case mission name changes between versions. */ xmlw_elem(writer, "done", m->name); } xmlw_endElem(writer); /* missions_done. */ return 0; } /** * @brief Save a ship. * @param writer XML writer. * @param ship Ship to save. * @param loc Location of the ship. * @return 0 on success. */ static int player_saveShip(xmlTextWriterPtr writer, Pilot* ship, char* loc) { int i, j, k; int found; xmlw_startElem(writer, "ship"); xmlw_attr(writer, "name", ship->name); xmlw_attr(writer, "model", ship->ship->name); if(loc != NULL) xmlw_elem(writer, "location", loc); /* Save the fuel. */ xmlw_elem(writer, "fuel", "%f", ship->fuel); /* Save the outfits. */ xmlw_startElem(writer, "outfits"); for(i = 0; i < ship->noutfits; i++) { xmlw_startElem(writer, "outfit"); xmlw_attr(writer, "quantity", "%d", ship->outfits[i].quantity); xmlw_str(writer, ship->outfits[i].outfit->name); xmlw_endElem(writer); /* Outfit. */ } xmlw_endElem(writer); /* Outfits. */ /* Save the commodities. */ xmlw_startElem(writer, "commodities"); for(i = 0; i < ship->ncommodities; i++) { /* Remove cargo with id and no mission. */ if(ship->commodities[i].id > 0) { found = 0; for(j = 0; j < MISSION_MAX; j++) { /* Only check active missions. */ if(player_missions[j].id > 0) { /* Now check if it's in the cargo list. */ for(k = 0; player_missions[j].ncargo; k++) { /* See if it matches a cargo. */ if(player_missions[j].cargo[k] == ship->commodities[i].id) { found = 1; break; } } } if(found) break; } if(!found) { WARN("Found mission cargo without assosciated mission."); WARN("Please save game to remove the dead cargo."); continue; } } xmlw_startElem(writer, "commodity"); xmlw_attr(writer, "quantity", "%d", ship->commodities[i].quantity); if(ship->commodities[i].id > 0) xmlw_attr(writer, "id", "%d", ship->commodities[i].id); xmlw_str(writer, ship->commodities[i].commodity->name); xmlw_endElem(writer); /* Commodity. */ } xmlw_endElem(writer); /* Commodities. */ xmlw_endElem(writer); /* Ship. */ return 0; } int player_load(xmlNodePtr parent) { xmlNodePtr node; /* Some cleanup. */ player_flags = 0; player_cleanup(); var_cleanup(); missions_cleanup(); map_close(); node = parent->xmlChildrenNode; do { if(xml_isNode(node, "player")) player_parse(node); else if(xml_isNode(node, "missions_done")) player_parseDone(node); } while(xml_nextNode(node)); return 0; } static int player_parse(xmlNodePtr parent) { unsigned int player_time; char* planet; Planet* pnt; int sw, sh; xmlNodePtr node, cur; node = parent->xmlChildrenNode; xmlr_attr(parent, "name", player_name); do { /* Global stuff. */ xmlr_float(node, "rating", player_crating); xmlr_int(node, "credits", player_credits); xmlr_long(node, "time", player_time); xmlr_str(node, "location", planet); if(xml_isNode(node, "ship")) player_parseShip(node, 1); else if(xml_isNode(node, "ships")) { cur = node->xmlChildrenNode; do { if(xml_isNode(cur, "ship")) player_parseShip(cur, 0); } while(xml_nextNode(cur)); } else if(xml_isNode(node, "licenses")) player_parseLicenses(node); } while(xml_nextNode(node)); /* Set global thingies. */ player->credits = player_credits; ltime_set(player_time); /* Set player in system. */ pnt = planet_get(planet); sw = pnt->gfx_space->sw; sh = pnt->gfx_space->sh; player_warp(pnt->pos.x + RNG(-sw/2, sw/2), pnt->pos.y + RNG(-sh/2, sh/2)); player->solid->dir = RNG(0, 359) * M_PI/180.; gl_bindCamera(&player->solid->pos); /* Initialize the system. */ music_choose("takeoff"); space_init(planet_getSystem(planet)); map_clear(); /* Sets the map up. */ /* Initialize the sound. */ player_initSound(); return 0; } static int player_parseDone(xmlNodePtr parent) { xmlNodePtr node; node = parent->xmlChildrenNode; do { if(xml_isNode(node, "done")) player_missionFinished(mission_getID(xml_get(node))); } while(xml_nextNode(node)); return 0; } /** * @brief Parse player's licenses. * @param parent Node of the licenses. * @return 0 on success. */ static int player_parseLicenses(xmlNodePtr parent) { xmlNodePtr node; node = parent->xmlChildrenNode; do { if(xml_isNode(node, "license")) player_addLicense(xml_get(node)); } while(xml_nextNode(node)); return 0; } static int player_parseShip(xmlNodePtr parent, int is_player) { char* name, *model, *loc, *q, *id; int i, n; double fuel; Pilot* ship; Outfit* o; xmlNodePtr node, cur; xmlr_attr(parent, "name", name); xmlr_attr(parent, "model", model); /* Player is currently on this ship. */ if(is_player != 0) { pilot_create(ship_get(model), name, faction_get("Player"), NULL, 0., NULL, NULL, PILOT_PLAYER | PILOT_NO_OUTFITS); ship = player; } else ship = pilot_createEmpty(ship_get(model), name, faction_get("Player"), NULL, PILOT_PLAYER | PILOT_NO_OUTFITS); free(name); free(model); node = parent->xmlChildrenNode; fuel = 0; do { if(!is_player == 0) xmlr_str(node, "location", loc); /* Get fuel. */ xmlr_float(node, "fuel", fuel); if(xml_isNode(node, "outfits")) { cur = node->xmlChildrenNode; do { /* Load each outfit. */ if(xml_isNode(cur, "outfits")) { xmlr_attr(cur, "quantity", q); n = atoi(q); free(q); /* Add the outfit. */ o = outfit_get(xml_get(cur)); if(o != NULL) pilot_addOutfit(ship, o, n); } } while(xml_nextNode(cur)); } if(xml_isNode(node, "commodities")) { cur = node->xmlChildrenNode; do { if(xml_isNode(cur, "commodity")) { xmlr_attr(cur, "quantity", q); xmlr_attr(cur, "id", id); n = atoi(q); if(id == NULL) i = 0; else i = atoi(id); free(q); if(id != NULL) free(id); /* Actually add the cargo with id hack. */ pilot_addCargo(ship, commodity_get(xml_get(cur)), n); if(i != 0) ship->commodities[ship->ncommodities-1].id = i; } } while(xml_nextNode(node)); } } while(xml_nextNode(node)); /* Set fuel. */ if(fuel != 0) ship->fuel = MIN(ship->fuel_max, fuel); /* Add it to the stack if it's not what the player is in. */ if(is_player == 0) { player_stack = realloc(player_stack, sizeof(Pilot*)*(player_nstack+1)); player_stack[player_nstack] = ship; player_lstack = realloc(player_lstack, sizeof(char*)*(player_nstack+1)); player_lstack[player_nstack] = strdup(loc); player_nstack++; } return 0; }