/** * @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 "player.h" #define XML_GUI_ID "GUIs" /**< XML section identifier for GUI document. */ #define XML_GUI_TAG "gui" /**< XML section identifier for GUI tags. */ #define XML_START_ID "Start" /**< Module start xml document identifier. */ #define GUI_DATA "../dat/gui.xml" /**< Global GUI configuration file. */ #define GUI_GFX "../gfx/gui/" /**< Location of the GUI graphics. */ #define START_DATA "../dat/start.xml" /**< Module start information file. */ #define TARGET_WIDTH 128 /**< Width of target graphics. */ #define TARGET_HEIGHT 96 /**< Height of target graphics. */ #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. */ #define INTERFERENCE_LAYERS 16 /**< Number of interference layers. */ #define INTERFERENCE_CHANGE_DT 0.1 /**< Speed to change at. */ /* Player stuff. */ Pilot* player = NULL; /**< The player. */ static Ship* player_ship = NULL; /**< Temp ship to hold when naming it. */ static double player_px; /**< Temp X position. */ static double player_py; /**< Temp Y position. */ static double player_vx; /**< Temp X velocity. */ static double player_vy; /**< Temp Y velocity. */ static double player_dir; /**< Temp direction. */ static int player_credits = 0; /**< Temp hack on create. */ static char* player_mission = NULL; /**< More hack. */ /* Licenses. */ static char** player_licenses = NULL; /**< Licenses player has. */ static int player_nlicenses = 0; /**< Number of licenses player has. */ /* * Player sounds. */ static int snd_target = -1; static int snd_jump = -1; static int snd_nav = -1; /* 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_turn = 0.; /**< Turn velocity from input. */ 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 unsigned int player_timer = 0; /**< For death and such. */ static Vec2 player_cam; /**< Again, for death etc. */ /* For interference. */ static int interference_layer = 0; /**< Layer of the current interference. */ double interference_alpha = 0.; /**< Alpha of the current interference layer. */ static double interference_t = 0.; /**< Interference timer to control transitions. */ 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. */ /* Pilot stuff for GUI. */ extern Pilot** pilot_stack; extern int pilot_nstack; /* Space stuff for GUI. */ extern StarSystem* systems_stack; /* Map stuff for autonav. */ extern int map_npath; /** * @struct Radar * * @brief Represents the player's radar. */ typedef struct Radar_ { double x; /**< X position. */ double y; /**< Y position. */ double w; /**< Width. */ double h; /**< Height. */ RadarShape shape; /**< Shape. */ double res; /**< Resolution. */ glTexture* interference[INTERFERENCE_LAYERS]; /**< Interference texture. */ } Radar; /* Radar res. */ #define RADAR_RES_MAX 100. /**< Max radar resolution. */ #define RADAR_RES_MIN 10. /**< Min radar resolution. */ #define RADAR_RES_INTERVAL 10. /**< Steps used to increase/decrease resolution. */ #define RADAR_RES_DEFAULT 40. /**< Default resolution. */ /** * @struct Rect * * @brief Represents a rectangle. */ typedef struct Rect_ { double x; /**< X position. */ double y; /**< Y position. */ double w; /**< Width. */ double h; /**< Height. */ } Rect; /** * @struct GUI * * @brief Represents the ingame player graphical user interface. */ typedef struct GUI_ { /* Graphics. */ glTexture* gfx_frame; /**< Frame of the GUI. */ glTexture* gfx_targetPilot, *gfx_targetPlanet; /**< Graphics used to target planets. */ /* Rects. */ /* Radar. */ Radar radar; /**< The radar. */ /* Navigation. */ Rect nav; /**< Navigation computer. */ /* Health. */ Rect shield; /**< Shield bar. */ glTexture* gfx_shield; /**< Shield bar texture if applicable. */ Rect armour; /**< Armour bar. */ glTexture* gfx_armour; /**M Armour bar texture if applicable. */ Rect energy; /**< Energy bar. */ glTexture* gfx_energy; /**< Energy bar texture if applicable. */ Rect fuel; /**< Fuel bar. */ glTexture* gfx_fuel; /**< Fuel bar texture if applicable. */ /* Weapon. */ Rect weapon; /**< Weapon targeting system. */ /* Targetting. */ Rect target_health; /**< Target health. */ Rect target_name; /**< Name of the target. */ Rect target_faction; /**< Faction of the target. */ /* Misc. */ Rect misc; /**< Misc stuff: credits, cargo.. */ /* Messages. */ Rect msg; /**< Where messages go. */ /* Positions. */ Vec2 frame; /**< Global frame position. */ Vec2 target; /**< Global target position. */ } GUI; static GUI gui = { .gfx_frame = NULL, .gfx_targetPilot = NULL, .gfx_targetPlanet = NULL }; /**< The GUI. */ /* Needed to render properly. */ double gui_xoff = 0.; /**< X offset that GUI introduces. */ double gui_yoff = 0.; /**< Y offset that GUI introduces. */ /* Messages. */ #define MSG_SIZE_MAX 120 /**< Max message length. */ int msg_timeout = 5000; /**< How long it takes for a message to timeout. */ int msg_max = 5; /**< Max messages on screen. */ /** * @struct Mesg * * @brief On screen player message. */ typedef struct Msg_ { char str[MSG_SIZE_MAX]; /**< The message. */ unsigned int t; /**< Time of creation. */ } Msg; static Msg* msg_stack; /**< Stack of messages, will be of mesg_max size. */ /* External. */ extern void pilot_render(const Pilot* pilot); /* Extern is in Pilot.* */ extern void weapon_minimap(const double res, const double w, const double h, const RadarShape shape, double alpha); /* weapon.c */ extern void planets_minimap(const double res, const double w, const double h, const RadarShape shape, double alpha); /* space.c */ /* Internal. */ /* Creation. */ static int player_newMake(void); static void player_newShipMake(char* name); /* Sound. */ static void player_initSound(void); /* Gui. */ static void gui_createInterference(void); static void rect_parseParam(const xmlNodePtr parent, char* name, double* param); static void rect_parse(const xmlNodePtr parent, double* x, double* y, double* w, double* h); static int gui_parse(const xmlNodePtr parent, const char* name); static void gui_cleanup(void); /* Render GUI. */ static void gui_renderPilot(const Pilot* p); static void gui_renderHealth(const glColour* c, const Rect* r, const glTexture* tex, const double w); static void gui_renderInterference(double dt); /* 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); gui.radar.res = RADAR_RES_DEFAULT; memset(msg_stack, 0, msg_max * sizeof(Msg)); /* 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(); /* 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) { pilot_stack[j] = player = 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 messages. */ for(i = 0; i < msg_max; i++) memset(msg_stack[i].str, '\0', MSG_SIZE_MAX); /* Clean up the stack. */ if(player_stack != NULL) { 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_done != NULL) { 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; } /* Clean up interference. */ interference_alpha = 0.; interference_layer = 0; interference_t = 0.; } 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 Add a msg to the queue to be displayed on screen. * @param fmt String with formatting like prinf. */ void player_message(const char* fmt, ...) { va_list ap; int i; if(fmt == NULL) return; /* Message not valid. */ /* Copy old messages back. */ for(i = 1; i < msg_max; i++) { if(msg_stack[msg_max-i-1].str[0] != '\0') { strcpy(msg_stack[msg_max-i].str, msg_stack[msg_max-i-1].str); msg_stack[msg_max-i].t = msg_stack[msg_max-i-1].t; } } /* Add the new one. */ va_start(ap, fmt); vsprintf(msg_stack[0].str, fmt, ap); va_end(ap); msg_stack[0].t = SDL_GetTicks() + msg_timeout; } /** * @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 background player stuff, namely planet target gfx. */ void player_renderBG(void) { double x, y; glColour* c; Planet* planet; if(player_isFlag(PLAYER_DESTROYED) || player_isFlag(PLAYER_CREATING) || pilot_isFlag(player, PLAYER_DESTROYED)) return; if(planet_target >= 0) { planet = cur_system->planets[planet_target]; c = faction_getColour(planet->faction); x = planet->pos.x - planet->gfx_space->sw/2.; y = planet->pos.y + planet->gfx_space->sh/2.; gl_blitSprite(gui.gfx_targetPlanet, x, y, 0, 0, c); /* Top left. */ x += planet->gfx_space->sw; gl_blitSprite(gui.gfx_targetPlanet, x, y, 1, 0, c); /* Top right. */ y -= planet->gfx_space->sh; gl_blitSprite(gui.gfx_targetPlanet, x, y, 1, 1, c); /* Bottom right. */ x -= planet->gfx_space->sw; gl_blitSprite(gui.gfx_targetPlanet, x, y, 0, 1, c); /* Bottom left. */ } } /** * @brief Render the player. */ void player_render(void) { Pilot* p; glColour* c; double x, y; if((player != NULL) && !player_isFlag(PLAYER_CREATING)) { /* Render the player target graphics. */ if(player->target != PLAYER_ID) p = pilot_get(player->target); else p = NULL; if((p == NULL) || pilot_isFlag(p, PILOT_DEAD)) player->target = PLAYER_ID; /* No more pilot target. */ else { /* There is still a pilot target. */ if(pilot_isDisabled(p)) c = &cInert; else if(pilot_isFlag(p, PILOT_BRIBED)) c = &cNeutral; else if(pilot_isFlag(p, PILOT_HOSTILE)) c = &cHostile; else c = faction_getColour(p->faction); x = p->solid->pos.x - p->ship->gfx_space->sw * PILOT_SIZE_APROX/2.; y = p->solid->pos.y + p->ship->gfx_space->sh * PILOT_SIZE_APROX/2.; gl_blitSprite(gui.gfx_targetPilot, x, y, 0, 0, c); /* Top left. */ x += p->ship->gfx_space->sw * PILOT_SIZE_APROX; gl_blitSprite(gui.gfx_targetPilot, x, y, 1, 0, c); /* Top right. */ y -= p->ship->gfx_space->sh * PILOT_SIZE_APROX; gl_blitSprite(gui.gfx_targetPilot, x, y, 1, 1, c); /* Bottom right. */ x -= p->ship->gfx_space->sw * PILOT_SIZE_APROX; gl_blitSprite(gui.gfx_targetPilot, x, y, 0, 1, c); /* Bottom left. */ } /* Player is ontop of targeting graphic. */ pilot_render(player); } } static int can_jump = 0; /**< Store whether or not the player is able to jump. */ /** * @brief Render the players GUI. */ void player_renderGUI(double dt) { int i, j; double x, y; char str[10]; Pilot* p; glColour* c, c2; glFont* f; StarSystem* sys; unsigned int t; int quantity, delay; t = SDL_GetTicks(); /* Pilot is dead or being created, just render her and stop. */ if(player_isFlag(PLAYER_DESTROYED) || player_isFlag(PLAYER_CREATING) || pilot_isFlag(player, PILOT_DEAD)) { if(player_isFlag(PLAYER_DESTROYED)) { if(!toolkit && !player_isFlag(PLAYER_CREATING) && (t > player_timer)) { menu_death(); } } /* Fancy cinematic scene borders. */ spfx_cinematic(); return; } if(player == NULL) return; /* Lockon warning. */ if(player->lockons > 0) gl_printMid(NULL, SCREEN_W - gui_xoff, 0., SCREEN_H-gl_defFont.h-25., &cRed, "LOCKON DETECTED"); /* Volatile Environment. */ if(cur_system->nebu_volatility > 0.) gl_printMid(NULL, SCREEN_W - gui_xoff, 0., SCREEN_H-gl_defFont.h*2.-35., &cRed, "VOLATILE ENVIRONMENT DETECTED"); /* GUI! */ /* -- Frame. */ gl_blitStatic(gui.gfx_frame, gui.frame.x, gui.frame.y, NULL); /* -- Radar. */ glMatrixMode(GL_PROJECTION); glPushMatrix(); if(gui.radar.shape == RADAR_RECT) glTranslated(gui.radar.x - SCREEN_W/2. + gui.radar.w/2., gui.radar.y - SCREEN_H/2. - gui.radar.h/2., 0.); else if(gui.radar.shape == RADAR_CIRCLE) glTranslated(gui.radar.x - SCREEN_W/2., gui.radar.y - SCREEN_H/2., 0.); /* Planets. */ planets_minimap(gui.radar.res, gui.radar.w, gui.radar.h, gui.radar.shape, 1.-interference_alpha); /* Weapons. */ weapon_minimap(gui.radar.res, gui.radar.w, gui.radar.h, gui.radar.shape, 1.-interference_alpha); /* Render the pilot_nstack. */ j = 0; for(i = 1; i < pilot_nstack; i++) { /* Skip the player. */ if(pilot_stack[i]->id == player->target) j = i; else gui_renderPilot(pilot_stack[i]); } /* Render the targetted pilot. */ if(j != 0) gui_renderPilot(pilot_stack[j]); glPopMatrix(); /* Interference. */ gui_renderInterference(dt); glPushMatrix(); if(gui.radar.shape == RADAR_RECT) glTranslated(gui.radar.x - SCREEN_W/2. + gui.radar.w/2., gui.radar.y - SCREEN_H/2. - gui.radar.h/2., 0.); else if(gui.radar.shape == RADAR_CIRCLE) glTranslated(gui.radar.x - SCREEN_W/2., gui.radar.y - SCREEN_H/2., 0.); /* The + sign in the middle of the radar represents the player. */ glBegin(GL_LINES); COLOUR(cRadar_player); glVertex2d( 0., -3.); glVertex2d( 0., 3.); glVertex2d(-3., 0.); glVertex2d( 3., 0.); glEnd(); glPopMatrix(); /* GL_PROJECTION. */ /* Nav. */ if(planet_target >= 0) { /* Planet landing target. */ gl_printMid(NULL, (int)gui.nav.w, gui.nav.x, gui.nav.y - 5, &cConsole, "Land"); gl_printMid(&gl_smallFont, (int)gui.nav.w, gui.nav.x, gui.nav.y - 10 - gl_smallFont.h, NULL, "%s", cur_system->planets[planet_target]->name); } else if(hyperspace_target >= 0) { /* Hyperspace target. */ sys = &systems_stack[cur_system->jumps[hyperspace_target]]; /* Determine if we have to play the "enter hyperspace range" sound. */ i = space_canHyperspace(player); if((i != 0) && (i != can_jump)) if(!pilot_isFlag(player, PILOT_HYPERSPACE)) player_playSound(snd_jump, 1); can_jump = i; /* Determine the colour of the NAV text. */ if(can_jump || pilot_isFlag(player, PILOT_HYPERSPACE) || pilot_isFlag(player, PILOT_HYP_PREP) || pilot_isFlag(player, PILOT_HYP_BEGIN)) c = &cConsole; else c = NULL; gl_printMid(NULL, (int)gui.nav.w, gui.nav.x, gui.nav.y - 5, c, "Hyperspace"); gl_printMid(&gl_smallFont, (int)gui.nav.w, gui.nav.x, gui.nav.y - 10 - gl_smallFont.h, NULL, "%d - %s", pilot_getJumps(player), (sys_isKnown(sys)) ? sys->name : "Unknown"); } else { /* No NAV target. */ gl_printMid(NULL, (int)gui.nav.w, gui.nav.x, gui.nav.y - 5, &cConsole, "Navigation"); gl_printMid(&gl_smallFont, (int)gui.nav.w, gui.nav.x, gui.nav.y - 10 - gl_smallFont.h, &cGrey, "Off"); } /* Health */ gui_renderHealth(&cShield, &gui.shield, gui.gfx_shield, player->shield / player->shield_max); gui_renderHealth(&cArmour, &gui.armour, gui.gfx_armour, player->armour / player->armour_max); gui_renderHealth(&cEnergy, &gui.energy, gui.gfx_energy, player->energy / player->energy_max); gui_renderHealth(&cFuel, &gui.fuel, gui.gfx_fuel, player->fuel / player->fuel_max); /* Weapon. */ if(player->secondary == NULL) { gl_printMid(NULL, (int)gui.weapon.w, gui.weapon.x, gui.weapon.y - 5, &cConsole, "Secondary"); gl_printMid(&gl_smallFont, (int)gui.weapon.w, gui.weapon.x, gui.weapon.y - 10 - gl_defFont.h, &cGrey, "None"); } else { f = &gl_defFont; quantity = pilot_oquantity(player, player->secondary); delay = outfit_delay(player->secondary->outfit); /* Check to see if weapon is ready. */ if(player->secondary->timer > 0.) c = &cGrey; else c = &cConsole; /* Launcher. */ if(player->ammo != NULL) { /* Use the ammunitions name. */ i = gl_printWidth(f, "%s", outfit_ammo(player->secondary->outfit)->name); if(i > gui.weapon.w) /* Font is too big. */ f = &gl_smallFont; /* Weapon name. */ gl_printMid(f, (int)gui.weapon.w, gui.weapon.x, gui.weapon.y - 5, (player->ammo) ? c : &cGrey, "%s", outfit_ammo(player->secondary->outfit)->name); /* Print ammo underneath to the left. */ gl_printMid(&gl_smallFont, (int)gui.weapon.w, gui.weapon.x, gui.weapon.y - 10 - gl_defFont.h, NULL, "%d", (player->ammo) ? player->ammo->quantity : 0); /* Other. */ } else { /* Just print the item name. */ i = gl_printWidth(f, "%s", player->secondary->outfit->name); if(i > (int)gui.weapon.w) /* Font is too big. */ f = &gl_smallFont; gl_printMid(f, (int)gui.weapon.w, gui.weapon.x, gui.weapon.y - (gui.weapon.h - f->h)/2., c, "%s", player->secondary->outfit->name); } } /* Target. */ if(player->target != PLAYER_ID) { p = pilot_get(player->target); /* Blit the pilot target. */ gl_blitStatic(p->ship->gfx_target, gui.target.x, gui.target.y, NULL); /* Blit the pilot space image. */ /*x = gui.target.x + (TARGET_WIDTH - p->ship->gfx_space->sw)/2.; y = gui.target.y + (TARGET_HEIGHT - p->ship->gfx_space->sh)/2.; gl_blitStaticSprite(p->ship->gfx_space, x, y, p->tsx, p->tsy, NULL);*/ /* Target name. */ gl_print(NULL, gui.target_name.x, gui.target_name.y, NULL, "%s", p->name); gl_print(&gl_smallFont, gui.target_faction.x, gui.target_faction.y, NULL, "%s", faction_name(p->faction)); /* Target status. */ if(pilot_isDisabled(p)) /* Disable the pilot. */ gl_print(&gl_smallFont, gui.target_health.x, gui.target_health.y, NULL, "Disabled"); else if(p->shield > p->shield_max / 100.) /* On shields. */ gl_print(&gl_smallFont, gui.target_health.x, gui.target_health.y, NULL, "%s: %.0f%%", "Shield", p->shield/p->shield_max*100.); else /* On armour. */ gl_print(&gl_smallFont, gui.target_health.x, gui.target_health.y, NULL, "%s: %.0f%%", "Armor", p->armour/p->armour_max*100.); } else { /* No target. */ gl_printMid(NULL, SHIP_TARGET_W, gui.target.x, gui.target.y + (SHIP_TARGET_H - gl_defFont.h)/2., &cGrey80, "No Target"); } /* Misc. */ j = gui.misc.y - 8 - gl_smallFont.h; gl_print(NULL, gui.misc.x + 8, j, &cConsole, "SCreds:"); credits2str(str, player->credits, 2); i = gl_printWidth(&gl_smallFont, str); gl_print(&gl_smallFont, gui.misc.x + gui.misc.w - 8 - i, j, NULL, str); /* Cargo and co. */ if(player->ncommodities > 0) { j -= gl_smallFont.h + 5; gl_print(&gl_smallFont, gui.misc.x + 8, j, &cConsole, "Cargo"); for(i = 0; i < MIN(player->ncommodities,3); i++) { j -= gl_smallFont.h + 3; if(player->commodities[i].quantity) /* Quantity is over. */ gl_printMax(&gl_smallFont, gui.misc.w - 15, gui.misc.x + 13, j, NULL, "%d %s%s", player->commodities[i].quantity, player->commodities[i].commodity->name, (player->commodities[i].id) ? "*" : ""); else /* Basically for weightless mission stuff. */ gl_printMax(&gl_smallFont, gui.misc.w - 15, gui.misc.x + 13, j, NULL, "%s%s", player->commodities[i].commodity->name, (player->commodities[i].id) ? "*" : ""); } } j -= gl_smallFont.h + 5; gl_print(&gl_smallFont, gui.misc.x + 8, j, &cConsole, "Free:"); i = gl_printWidth(&gl_smallFont, "%d", pilot_cargoFree(player)); gl_print(&gl_smallFont, gui.misc.x + gui.misc.w - 8 - i, j, NULL, "%d", pilot_cargoFree(player)); /* Messages. */ x = gui.msg.x; y = gui.msg.y + (double)(gl_defFont.h * msg_max)*1.2; c2.r = c2.g = c2.b = 1.; for(i = 0; i < msg_max; i++) { y -= (double)gl_defFont.h*1.2; if(msg_stack[msg_max-i-1].str[0] != '\0') { if(msg_stack[msg_max-i-1].t < t) msg_stack[msg_max-i-1].str[0] = '\0'; else { if(msg_stack[msg_max-i-1].t - msg_timeout/2 < t) c2.a = (double)(msg_stack[msg_max-i-1].t - t) / (double)(msg_timeout/2); else c2.a = 1.; gl_print(NULL, x, y, &c2, "%s", msg_stack[msg_max-i-1].str); } } } /* Hyperspace FLASH BANG!!! */ if(pilot_isFlag(player, PILOT_HYPERSPACE) && (player->ptimer < HYPERSPACE_FADEOUT)) { if(i < j) { x = (HYPERSPACE_FADEOUT-player->ptimer) / HYPERSPACE_FADEOUT; glColor4d(1.,1.,1., x); /* We'll | I'll, make this more effiecent later. */ glBegin(GL_QUADS); glVertex2d(-SCREEN_W/2., -SCREEN_H/2.); glVertex2d(-SCREEN_W/2., SCREEN_H/2.); glVertex2d( SCREEN_W/2., SCREEN_H/2.); glVertex2d( SCREEN_W/2., -SCREEN_H/2.); glEnd(); } } } /** * @brief Render interference if needed. * @param dt Current deltatick. */ static void gui_renderInterference(double dt) { glColour c; glTexture* tex; int t; /* Must be displaying inteference. */ if(interference_alpha <= 0.) return; /* Calculate frame to draw. */ interference_t += dt; if(interference_t > INTERFERENCE_CHANGE_DT) { /* Time to change. */ t = RNG(0, INTERFERENCE_LAYERS-1); if(t != interference_layer) interference_layer = t; else interference_layer = (interference_layer == INTERFERENCE_LAYERS-1) ? 0 : interference_layer+1; interference_t -= INTERFERENCE_CHANGE_DT; } /* Render the interference. */ c.r = c.g = c.b = 1.; c.a = interference_alpha; tex = gui.radar.interference[interference_layer]; if(gui.radar.shape == RADAR_CIRCLE) gl_blitStatic(tex, gui.radar.x - gui.radar.w, gui.radar.y - gui.radar.w, &c); else if(gui.radar.shape == RADAR_RECT) gl_blitStatic(tex, gui.radar.x - gui.radar.w / 2, gui.radar.y - gui.radar.h/2, &c); } /* Renders a pilot. */ static void gui_renderPilot(const Pilot* p) { int x, y, sx, sy; double w, h; glColour* col; double a; /* Get position. */ x = (p->solid->pos.x - player->solid->pos.x) / gui.radar.res; y = (p->solid->pos.y - player->solid->pos.y) / gui.radar.res; /* Get size. */ sx = PILOT_SIZE_APROX/2. * p->ship->gfx_space->sw / gui.radar.res; sy = PILOT_SIZE_APROX/2. * p->ship->gfx_space->sh / gui.radar.res; if(sx < 1.) sx = 1.; if(sy < 1.) sy = 1.; /* Check if pilot in range. */ if(((gui.radar.shape == RADAR_RECT) && ((ABS(x) > gui.radar.w/2.+sx) || (ABS(y) > gui.radar.h/2.+sy))) || ((gui.radar.shape == RADAR_CIRCLE) && ((x*x + y*y) > (int)(gui.radar.w*gui.radar.w)))) { /* Draw a little targetted symbol. */ if(p->id == player->target) { /* Circle radars have it easy. */ if(gui.radar.shape == RADAR_CIRCLE) { /* We'll create a line. */ a = ANGLE(x, y); x = gui.radar.w*cos(a); y = gui.radar.w*sin(a); sx = 0.85 * x; sy = 0.85 * y; COLOUR(cRadar_tPilot); glBegin(GL_LINES); glVertex2d(x, y); glVertex2d(sx, sy); glEnd(); } } return; } if(gui.radar.shape == RADAR_RECT) { w = gui.radar.w/2.; h = gui.radar.h/2.; } else if(gui.radar.shape == RADAR_CIRCLE) { w = gui.radar.w; h = gui.radar.w; } glBegin(GL_QUADS); /* Colours. */ if(p->id == player->target) col = &cRadar_tPilot; else if(pilot_isDisabled(p)) col = &cInert; else if(pilot_isFlag(p, PILOT_BRIBED)) col = &cNeutral; else if(pilot_isFlag(p, PILOT_HOSTILE)) col = &cHostile; else col = faction_getColour(p->faction); ACOLOUR(*col, 1-interference_alpha); /**< Makes it much harder to see. */ /* Image. */ glVertex2d(MAX(x-sx, -w), MIN(y+sy, h)); /* Top left. */ glVertex2d(MIN(x+sx, w), MIN(y+sy, h)); /* Top right. */ glVertex2d(MIN(x+sx, w), MAX(y-sy, -h)); /* Bottom right. */ glVertex2d(MAX(x-sx, -w), MAX(y-sy, -h)); /* Bottom left. */ glEnd(); } /** * @brief Render a health bar. */ static void gui_renderHealth(const glColour* c, const Rect* r, const glTexture* tex, const double w) { double x, y, sx, sy, tx, ty; /* Set colour. */ COLOUR(*c); /* Just create a bar. */ if(tex == NULL) { /* Set the position values. */ x = r->x - SCREEN_W/2.; y = r->y - SCREEN_H/2.; sx = w * r->w; sy = r->h; glBegin(GL_QUADS); glVertex2d(x, y); glVertex2d(x+sx, y); glVertex2d(x+sx, y-sy); glVertex2d(x, y-sy); glEnd(); } else { /* Render the texture. */ /* Set the position values. */ x = r->x - SCREEN_W/2.; y = r->y - SCREEN_H/2. + tex->sh; sx = w * tex->sw; sy = tex->sh; tx = tex->sw / tex->rw; ty = tex->sh / tex->rh; /* Draw the image. */ glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tex->texture); glBegin(GL_QUADS); COLOUR(*c); glTexCoord2d(0., ty); glVertex2d(x, y); glTexCoord2d(w*tx, ty); glVertex2d(x + sx, y); glTexCoord2d(w*tx, 0.); glVertex2d(x+sx, y - sy); glTexCoord2d(0., 0.); glVertex2d(x, y - sy); glEnd(); glDisable(GL_TEXTURE_2D); } } /** * @brief Initialize the GUI system. * @return 0 on success. */ int gui_init(void) { /* Set graphics to NULL. */ memset(&gui, 0, sizeof(GUI)); /* -- Radar. */ gui.radar.res = RADAR_RES_DEFAULT; /* -- messages. */ gui.msg.x = 20; gui.msg.y = 30; msg_stack = calloc(msg_max, sizeof(Msg)); if(msg_stack == NULL) { ERR("Out of memory!"); return -1; } return 0; } /** * @brief Attempt to load the actual GUI. * @brief name Name of the GUI to load. * @brief 0 on success. */ int gui_load(const char* name) { uint32_t bufsize; char* buf = ldata_read(GUI_DATA, &bufsize); char* tmp; int found = 0; xmlNodePtr node; xmlDocPtr doc = xmlParseMemory(buf, bufsize); node = doc->xmlChildrenNode; if(!xml_isNode(node, XML_GUI_ID)) { ERR("Malformed '"GUI_DATA"' file: missing root element '"XML_GUI_ID"'"); return -1; } node = node->xmlChildrenNode; /* First system node. */ if(node == NULL) { ERR("Malformed '"GUI_DATA"' file: does not contain elements"); return -1; } do { if(xml_isNode(node, XML_GUI_TAG)) { tmp = xml_nodeProp(node, "name"); /* Mallocs. */ /* Is this the gui we are looking for? */ if(strcmp(tmp, name)==0) { found = 1; /* Parse the xml node. */ if(gui_parse(node, name)) WARN("Trouble loading GUI '%s'", name); free(tmp); break; } free(tmp); } } while((node = node->next)); xmlFreeDoc(doc); free(buf); if(!found) { WARN("GUI '%s' not found in '"GUI_DATA"'",name); return -1; } return 0; } /** * @brief Parse a parameter of the rect node. */ static void rect_parseParam(const xmlNodePtr parent, char* name, double* param) { char* buf; /* Get the attribute. */ xmlr_attr(parent, name, buf); /* Wants attribute. */ if(param != NULL) { if(buf == NULL) WARN("Node '%s' missing 'x' parameter.", parent->name); else if(buf != NULL) *param = atoi(buf); } /* Doesn't want it. */ else if(buf != NULL) WARN("Node '%s' has superfluous 'x' parameter.", parent->name); /* Clean up. */ if(buf != NULL) free(buf); } /** * @brief Used to pull out a rect from an xml node. */ static void rect_parse(const xmlNodePtr parent, double* x, double* y, double* w, double* h) { rect_parseParam(parent, "w", w); rect_parseParam(parent, "h", h); rect_parseParam(parent, "x", x); rect_parseParam(parent, "y", y); } /** * @brief Create the interference map for the current gui. */ static void gui_createInterference(void) { uint8_t raw; int i, j, k; float* map; uint32_t* pix; SDL_Surface* sur; int w, h, hw, hh; float c; int r; /* Dimension shortcuts. */ if(gui.radar.shape == RADAR_CIRCLE) { w = gui.radar.w*2.; h = w; } else if(gui.radar.shape == RADAR_RECT) { w = gui.radar.w; h = gui.radar.h; } for(k = 0; k < INTERFERENCE_LAYERS; k++) { /* Free the old texture. */ if(gui.radar.interference[k] != NULL) gl_freeTexture(gui.radar.interference[k]); /* Create the temp surface. */ sur = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, RGBAMASK); pix = sur->pixels; /* Load the interference map. */ map = noise_genRadarInt(w, h, 100.); /* Create the texture. */ SDL_LockSurface(sur); if(gui.radar.shape == RADAR_CIRCLE) { r = pow2((int)gui.radar.w); hw = w/2; hh = h/2; for(i = 0; i < h; i++) { for(j = 0; j < w; j++) { /* Must be in circle. */ if(pow2(i-hh) + pow2(j-hw) > r) continue; c = map[i*w + j]; raw = 0xff & (uint8_t)((float)0xff * c); memset(&pix[i*w + j], raw, sizeof(uint32_t)); pix[i*w + j] |= AMASK; } } } else if(gui.radar.shape == RADAR_RECT) { for(i = 0; i < h*w; i++) { /* Process pixels. */ c = map[i]; raw = 0xff & (uint8_t)((float)0xff * c); memset(&pix[i], raw, sizeof(uint32_t)); pix[i] |= AMASK; } } SDL_UnlockSurface(sur); /* Set the interference. */ gui.radar.interference[k] = gl_loadImage(sur); /* Clean up. */ free(map); } } #define RELATIVIZE(a) \ { (a).x += VX(gui.frame); (a).y = VY(gui.frame) + gui.gfx_frame->h-(a).y; } /**< Converts a rect to absolute coords. */ /** * @brief Parses a gui node. * @param parent Node to parse from. * @param name Name of the GUI to load. */ static int gui_parse(const xmlNodePtr parent, const char* name) { xmlNodePtr cur, node; char* tmp, buf[PATH_MAX]; /* Clean up. */ gui_cleanup(); /* Gfx. */ /* Set a property and not a node because it must be loaded first. */ tmp = xml_nodeProp(parent, "gfx"); if(tmp == NULL) { ERR("GUI '%s' has no gfx property", name); return -1; } /* Load gfx. */ /* Frame. */ snprintf(buf, PATH_MAX, GUI_GFX"%s.png", tmp); gui.gfx_frame = gl_newImage(buf, 0); /* Pilot. */ snprintf(buf, PATH_MAX, GUI_GFX"%s_pilot.png", tmp); gui.gfx_targetPilot = gl_newSprite(buf, 2, 2, 0); /* Planet. */ snprintf(buf, PATH_MAX, GUI_GFX"%s_planet.png", tmp); gui.gfx_targetPlanet = gl_newSprite(buf, 2, 2, 0); free(tmp); /* Frame (based on gfx). */ vect_csetmin(&gui.frame, SCREEN_W - gui.gfx_frame->w, /* x. */ SCREEN_H - gui.gfx_frame->h); /* h. */ /* Let's parse the data now. */ node = parent->children; do { /* Offset. */ if(xml_isNode(node, "offset")) rect_parse(node, &gui_xoff, &gui_yoff, NULL, NULL); /* Radar. */ else if(xml_isNode(node, "radar")) { tmp = xml_nodeProp(node,"type"); /* Make sure type is valid. */ if(strcmp(tmp, "rectangle")==0) gui.radar.shape = RADAR_RECT; else if(strcmp(tmp, "circle")==0) gui.radar.shape = RADAR_CIRCLE; else { WARN("Radar for GUI '%s' is missing 'type' tag or has invalid 'type' tag", name); gui.radar.shape = RADAR_RECT; } free(tmp); /* Load the appropriate measurements. */ if(gui.radar.shape == RADAR_RECT) rect_parse(node, &gui.radar.x, &gui.radar.y, &gui.radar.w, &gui.radar.h); else if(gui.radar.shape == RADAR_CIRCLE) rect_parse(node, &gui.radar.x, &gui.radar.y, &gui.radar.w, NULL); RELATIVIZE(gui.radar); } /* Nav computer. */ else if(xml_isNode(node, "nav")) { rect_parse(node, &gui.nav.x, &gui.nav.y, &gui.nav.w, &gui.nav.h); RELATIVIZE(gui.nav); gui.nav.y -= gl_defFont.h; } /* Health bars. */ else if(xml_isNode(node, "health")) { cur = node->children; do { if(xml_isNode(cur, "shield")) { rect_parse(cur, &gui.shield.x, &gui.shield.y, &gui.shield.w, &gui.shield.h); tmp = xml_get(cur); if(tmp != NULL) { snprintf(buf, PATH_MAX, GUI_GFX"%s.png", tmp); gui.gfx_shield = gl_newImage(buf, 0); } RELATIVIZE(gui.shield); } if(xml_isNode(cur, "armour")) { rect_parse(cur, &gui.armour.x, &gui.armour.y, &gui.armour.w, &gui.armour.h); tmp = xml_get(cur); if(tmp != NULL) { snprintf(buf, PATH_MAX, GUI_GFX"%s.png", tmp); gui.gfx_armour = gl_newImage(buf, 0); } RELATIVIZE(gui.armour); } if(xml_isNode(cur, "energy")) { rect_parse(cur, &gui.energy.x, &gui.energy.y, &gui.energy.w, &gui.energy.h); tmp = xml_get(cur); if(tmp != NULL) { snprintf(buf, PATH_MAX, GUI_GFX"%s.png", tmp); gui.gfx_energy = gl_newImage(buf, 0); } RELATIVIZE(gui.energy); } if(xml_isNode(cur, "fuel")) { rect_parse(cur, &gui.fuel.x, &gui.fuel.y, &gui.fuel.w, &gui.fuel.h); tmp = xml_get(cur); if(tmp != NULL) { snprintf(buf, PATH_MAX, GUI_GFX"%s.png", tmp); gui.gfx_fuel = gl_newImage(buf, 0); } RELATIVIZE(gui.fuel); } } while((cur = cur->next)); } /* Secondary weapon. */ else if(xml_isNode(node, "weapon")) { rect_parse(node, &gui.weapon.x, &gui.weapon.y, &gui.weapon.w, &gui.weapon.h); RELATIVIZE(gui.weapon); gui.weapon.y -= gl_defFont.h; } /* Target. */ else if(xml_isNode(node, "target")) { cur = node->children; do { if(xml_isNode(cur, "gfx")) { rect_parse(cur, &gui.target.x, &gui.target.y, NULL, NULL); RELATIVIZE(gui.target); gui.target.y -= SHIP_TARGET_H; } if(xml_isNode(cur, "name")) { rect_parse(cur, &gui.target_name.x, &gui.target_name.y, NULL, NULL); RELATIVIZE(gui.target_name); gui.target_name.y -= gl_defFont.h; } if(xml_isNode(cur, "faction")) { rect_parse(cur, &gui.target_faction.x, &gui.target_faction.y, NULL, NULL); RELATIVIZE(gui.target_faction); gui.target_faction.y -= gl_smallFont.h; } if(xml_isNode(cur, "health")) { rect_parse(cur, &gui.target_health.x, &gui.target_health.y, NULL, NULL); RELATIVIZE(gui.target_health); gui.target_health.y -= gl_smallFont.h; } } while((cur = cur->next)); } else if(xml_isNode(node, "misc")) { rect_parse(node, &gui.misc.x, &gui.misc.y, &gui.misc.w, &gui.misc.h); RELATIVIZE(gui.misc); } } while(xml_nextNode(node)); /* Some postprocessing. */ gui_createInterference(); return 0; } #undef RELATIVIZE /** * @brief Clean up the GUI. */ static void gui_cleanup(void) { int i; /* Free textures. */ if(gui.gfx_frame != NULL) { gl_freeTexture(gui.gfx_frame); gui.gfx_frame = NULL; } if(gui.gfx_targetPilot != NULL) { gl_freeTexture(gui.gfx_targetPilot); gui.gfx_targetPilot = NULL; } if(gui.gfx_targetPlanet != NULL) { gl_freeTexture(gui.gfx_targetPlanet); gui.gfx_targetPlanet = NULL; } /* Health textures. */ if(gui.gfx_shield != NULL) { gl_freeTexture(gui.gfx_shield); gui.gfx_shield = NULL; } if(gui.gfx_armour != NULL) { gl_freeTexture(gui.gfx_armour); gui.gfx_armour = NULL; } if(gui.gfx_energy != NULL) { gl_freeTexture(gui.gfx_energy); gui.gfx_energy = NULL; } if(gui.gfx_fuel != NULL) { gl_freeTexture(gui.gfx_fuel); gui.gfx_fuel = NULL; } for(i = 0; i < INTERFERENCE_LAYERS; i++) { if(gui.radar.interference[i] != NULL) { gl_freeTexture(gui.radar.interference[i]); gui.radar.interference[i] = NULL; } } } /** * @brief Free the GUI. */ void gui_free(void) { /* Clean up gui. */ gui_cleanup(); /* Free messages. */ free(msg_stack); } /** * @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) { /* 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)) { if(pplayer->lockons > 0) player_abortAutonav("Missile Lockon Detected"); if(space_canHyperspace(pplayer)) { player_jump(); } else { pilot_face(pplayer, VANGLE(pplayer->solid->pos)); if(player_acc < 1.) player_accel(1.); } } /* PLAYER_FACE will take over navigation. */ else if(player_isFlag(PLAYER_FACE)) { if(player->target != PLAYER_ID) pilot_face(pplayer, vect_angle(&player->solid->pos, &pilot_get(player->target)->solid->pos)); 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) && (VMOD(pplayer->solid->vel) > 0.)) pilot_face(pplayer, VANGLE(player->solid->vel) + M_PI); /* Normal navigation sheme. */ else { pplayer->solid->dir_vel = 0.; if(player_turn) pplayer->solid->dir_vel -= player->turn * player_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 Modify the radar resolution. * @param mod Number of intervals to jump (up or down). */ void player_setRadarRel(int mod) { gui.radar.res += mod * RADAR_RES_INTERVAL; if(gui.radar.res > RADAR_RES_MAX) gui.radar.res = RADAR_RES_MAX; else if(gui.radar.res < RADAR_RES_MIN) gui.radar.res = RADAR_RES_MIN; player_message("Radar set to %dx.", (int)gui.radar.res); } /** * @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(&systems_stack[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(systems_stack[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; a = ANGLE(systems_stack[cur_system->jumps[hyperspace_target]].pos.x - cur_system->pos.x, systems_stack[cur_system->jumps[hyperspace_target]].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(pilot_isFlag(player, PILOT_HYP_PREP) || pilot_isFlag(player, PILOT_HYPERSPACE)) return; if(player != NULL) { 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 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 = SDL_GetTicks() + 5000; } /** * @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. */ 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; }