/* v1.1 * * smain.c: Main space loop. * * This program is free software and may be freely redistributed as * specified in the GNU General Public License. Please see the file * 'COPYING' for details. */ #include "spaceconf.h" #include "pseint.h" #include "dbint.h" #include "space.h" #include "class.h" #include "output.h" #include "object.h" #include "nav.h" #include "shields.h" #include "tactical.h" #include "eng.h" #include "damage.h" #include "smisc.h" #include "events.h" #include "version.h" #include "commands.h" #include "sensors.h" #include "flag.h" #include "rxl/rxl.h" #ifdef ENABLE_SHIELD_CLASSES #include "scm/scm.h" #endif /* * Global linked list pointers. */ SPACEINFO space_info[NUM_SPACES]; const char *system_names[NUM_SYSTEMS] = { "fore shield", "aft shield", "port shield", "starboard shield", "dorsal shield", "ventral shield", "gun 0", "gun 1", "gun 2", "gun 3", "gun 4", "gun 5", "torp 0", "torp 1", "torp 2", "torp 3", "torp 4", "torp 5", "batteries", "cloak", "engine", "sensors", "scanners", "hull" , "tractor beam", "transporters" }; static void move_objects(int); static void tactical_turn(int); static void update_timers(int); static void free_timer(TIMER *); static void handle_tractor_target(TAG *, TAG *, struct timeval *now); static void handle_tractor_source(TAG *, TAG *, struct timeval *now); int move_turn_id = 0; /* Movement turn ID. Used to speed up checking */ /* * Called from the MU* server when version information is requested. */ void spaceVersion(dbref player) { Notify(player, ENGINE_VERSION); return; } /* * Called from the MU* server to initialise the space engine. Should only be * called once. */ void spaceInit(void) { int space; /* initialise the space pointers and so on */ for (space = 0; space < NUM_SPACES; space++) { sprintf(space_info[space].name, "Space%d", space); space_info[space].flags = SPACE_ACTIVE; space_info[space].list = NULL; space_info[space].tail = NULL; space_info[space].dist = NULL; space_info[space].huge = NULL; space_info[space].timers = NULL; space_info[space].cycle = 0; } /* Support legacy naming and logging */ strcpy(space_info[0].name, "Real"); space_info[0].flags |= SPACE_LOGGED; clsInit(); cmdInitFunctionTable(); objInit(); flagInit(); #ifdef ENABLE_SHIELD_CLASSES scmInitModules(); #endif /* Initialise the range conversion modules */ rxlInitModules(); return; } /* * Called once per second by the MU* server. This code handles movement, and * other time-related operations, such as contacts and weapons. */ void spaceUpdate(void) { int current_space; for (current_space=0; current_space < NUM_SPACES; current_space++) { /* Make sure that the server parser is ready */ ResetEval(); #ifdef ENABLE_TURN_BASED /* Only run the cycle if the flag is set */ if ((space_info[current_space].flags & SPACE_DO_TURN) == 0) continue; /* Clear the flag as we're running the cycle */ space_info[current_space].flags &= ~SPACE_DO_TURN; #endif /* Update the cycle counter */ space_info[current_space].cycle++; update_timers(current_space); #ifdef ENABLE_REALTIME tactical_turn(current_space); move_objects(current_space); snsBuildDistanceTables(current_space); move_turn_id++; snsCheckSensors(current_space); evCheckEvents(current_space); damShipRepairs(current_space); objFreeMarkedObjects(current_space); #else switch (space_info[current_space].cycle % 6) { case 0: tactical_turn(current_space); break; case 1: move_objects(current_space); break; case 2: snsBuildDistanceTables(current_space); /* update the movement id, so movement caused from an event * is caught next time around. */ move_turn_id ++; snsCheckSensors(current_space); evCheckEvents(current_space); break; case 3: move_objects(current_space); break; case 4: damShipRepairs(current_space); break; case 5: move_objects(current_space); break; } objFreeMarkedObjects(current_space); #endif } return; } void move_objects(int space) { char *buff; TAG *object; float inc, einc, binc, max_speed, dist; SPH sph; XYZ xyz; float rr, rinc; struct timeval now; buff = pse_malloc(MAX_ATTRIBUTE_LEN); /* Grab the current timestamp */ if (gettimeofday(&now, NULL) != 0) { log_space("move_objects: gettimeofday failed. Update skipped."); return; } for (object=space_info[space].list; object != NULL; object=object->next) { /* If the object is flagged to be removed, then ignore it */ if (Removed(object)) continue; /* Objects without the 'CAN_MOVE' flag set can never move */ if (!CanMove(object)) continue; /* * Handle tractor beams. Ugh, so much for the nice concise * movement code. Ah, the cost of innovation! */ if (Ship(object) && (object->tractor_source != NULL)) handle_tractor_target(object->tractor_source, object, &now); if (Ship(object) && (object->shipdata->tractor_target != NULL)) handle_tractor_source(object, object->shipdata->tractor_target, &now); /* Handle change of direction */ if (ManualDirection(object)) { /* Make sure that the server parser is ready */ ResetEval(); getEvalAttrBuf(object->rec->db_obj, MOVE_HEAD_BEARING, (char **)NULL, 0, buff); sph.bearing = atof(buff); getEvalAttrBuf(object->rec->db_obj, MOVE_HEAD_ELEVATION, (char **)NULL, 0, buff); sph.elevation = atof(buff); sph.range = 1; if ((sph.bearing < 0) || (sph.bearing >= 360)) { log_space("#%d: Invalid manual bearing of %3.2f specified", object->rec->db_obj, sph.bearing); sph.bearing = object->heading.bearing; } if ((sph.elevation < -90) || (sph.elevation > 90)) { log_space("#%d: Invalid manual elevation of %3.2f specified", object->rec->db_obj, sph.elevation); sph.elevation = object->heading.elevation; } if ((sph.bearing != object->heading.bearing) || (sph.elevation != object->heading.elevation)) { object->heading = sph; object->turnID = move_turn_id; } getEvalAttrBuf(object->rec->db_obj, MOVE_ROLL, (char **)NULL, 0, buff); rr = atof(buff); if ((rr < 0) || (rr >= 360)) { log_space("#%d: Invalid manual rotation of %3.2f specified", object->rec->db_obj, rr); rr = object->roll; } if (rr != object->roll) { object->roll = rr; object->turnID = move_turn_id; } getEvalAttrBuf(object->rec->db_obj, MOVE_SPEED, (char **)NULL, 0, buff); object->speed = atof(buff); } else if (Ship(object)) { /* Adjust heading */ if (object->heading_adj.bearing != 0 || object->heading_adj.elevation != 0) { object->turnID = move_turn_id; inc = 90.0 * F_FLOAT(object->shipdata->rec, turn_factor) / (F_FLOAT(object->rec, size) * F_FLOAT(object->rec, size)); binc = Min(fabs(object->heading_adj.bearing), inc) * Sign(object->heading_adj.bearing); einc = Min(fabs(object->heading_adj.elevation), inc) * Sign(object->heading_adj.elevation); object->heading.bearing += binc; if (object->heading.bearing >= 360.0) object->heading.bearing -= 360.0; if (object->heading.bearing < 0) object->heading.bearing += 360.0; object->heading_adj.bearing -= binc; object->heading.elevation += einc; object->heading_adj.elevation -= einc; object->heading.range = 1; if (object->heading_adj.bearing==0 && object->heading_adj.elevation==0) MFNotify(object->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Now heading %3.2f%+3.2f]", object->heading.bearing, object->heading.elevation); } /* Adjust roll */ if (object->roll_adj != 0) { object->turnID = move_turn_id; /* Calculate the adjustment */ rinc = 90.0 * F_FLOAT(object->shipdata->rec, roll_factor) / (F_FLOAT(object->rec, size) * F_FLOAT(object->rec, size)); rinc = Min(fabs(object->roll_adj), rinc) * Sign(object->roll_adj); object->roll += rinc; /* Ensure the roll value is winth range */ if (object->roll >= 360.0) object->roll -= 360.0; if (object->roll < 0) object->roll += 360.0; /* Reduce the amount left to roll */ object->roll_adj -= rinc; /* Let the navigators know if a roll just finished */ if (object->roll_adj == 0.0) MFNotify(object->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Roll complete]"); } } /* If direction of travel changed, recalculate movement vector */ if (object->turnID == move_turn_id) { object->v_move[0] = cos(object->heading.bearing * PI/180.0) * cos(object->heading.elevation * PI / 180.0); object->v_move[1] = sin(object->heading.bearing * PI/180.0) * cos(object->heading.elevation * PI / 180.0); object->v_move[2] = sin(object->heading.elevation * PI/180.0); } /* Handle change of position */ if (ManualPosition(object)) { /* Make sure that the server parser is ready */ ResetEval(); getEvalAttrBuf(object->rec->db_obj, MOVE_POS_X, (char **)NULL, 0, buff); xyz.x = RANGEI(buff); getEvalAttrBuf(object->rec->db_obj, MOVE_POS_Y, (char **)NULL, 0, buff); xyz.y = RANGEI(buff); getEvalAttrBuf(object->rec->db_obj, MOVE_POS_Z, (char **)NULL, 0, buff); xyz.z = RANGEI(buff); if ((RANGEA(xyz.x) > MAX_RANGE) || (RANGEA(xyz.y) > MAX_RANGE) || (RANGEA(xyz.y) > MAX_RANGE)) { log_space("#%d: Invalid manual position (" RANGEF "," RANGEF "," RANGEF ")", object->rec->db_obj, xyz.x, xyz.y, xyz.z); } else if ((xyz.x != object->dc_pos.x) || (xyz.y != object->dc_pos.y) || (xyz.z != object->dc_pos.z)) { object->moveID = move_turn_id; #ifdef ENABLE_ODOMETER if (Ship(object)) F_INT(object->shipdata->rec, odometer) += distance(&xyz, &object->dc_pos); #endif object->dc_pos.x = xyz.x; object->dc_pos.y = xyz.y; object->dc_pos.z = xyz.z; object->dc_time.tv_sec = now.tv_sec; object->dc_time.tv_usec = now.tv_usec; } } else if (Ship(object)) { if (Pokey(object->shipdata)) { max_speed = navMaxWarp(object, object->shipdata->alloc_nav); if (max_speed < object->shipdata->warp_set_speed) { object->shipdata->warp_set_speed = max_speed; MFNotify(object->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Maximum speed reduced to %3.2f]", max_speed); } } /* Adjust speed if necessary */ inc = object->shipdata->warp_set_speed - object->shipdata->warp_speed; if (inc != 0.0) { object->shipdata->warp_speed += Min(inc * Sign(inc), F_FLOAT(object->shipdata->rec, warp_accel)) * Sign(inc); if (object->shipdata->warp_speed == object->shipdata->warp_set_speed) { MFNotify(object->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Speed now at warp %3.2f]", object->shipdata->warp_speed); } } object->speed = object->shipdata->warp_speed; // Update the object's position object_position(object, &pos, &now, 1); // Set the turn ID - used for sensor things. object->moveID = move_turn_id; /* Enforce the maximum size of space. Eventually sector * transitions will go here. */ if (RANGEA(object->dc_pos.x) > MAX_RANGE) object->dc_pos.x = MAX_RANGE * ((object->dc_pos.x > 0) ? 1:-1); if (RANGEA(object->dc_pos.y) > MAX_RANGE) object->dc_pos.y = MAX_RANGE * ((object->dc_pos.y > 0) ? 1:-1); if (RANGEA(object->dc_pos.x) > MAX_RANGE) object->dc_pos.z = MAX_RANGE * ((object->dc_pos.z > 0) ? 1:-1); } } pse_free(buff); return; } void tactical_turn(int space) { CONTACT *source; TAG *ptr; for (ptr = space_info[space].list; ptr != NULL; ptr=ptr->next) { if (Removed(ptr)) continue; if (Ship(ptr)) { engTacticalTurn(ptr); tacMaintainWeapons(ptr); shdMaintainShields(ptr); } /* resolve any pending lock */ if (ptr->pending_lock != NULL) { ptr->locked_on = ptr->pending_lock; ptr->pending_lock = NULL; source = snsFindContact(ptr->locked_on->listref, ptr); if (Ship(ptr)) MFNotify(ptr->shipdata, -1, CONS_TAC, CONS_ACTIVE, "[Lock achieved]"); if (Ship(ptr->locked_on->listref)) { if (source == NULL) MFNotify(ptr->locked_on->listref->shipdata, -1, CONS_TAC, CONS_ACTIVE, "[Weapons lock from unknown source completed]"); else MFNotify(ptr->locked_on->listref->shipdata, -1, CONS_TAC, CONS_ACTIVE, "[Weapons lock from contact [%d] %s completed]", source->contact_number, source->listref->rec->name); } if (EventDriven(ptr->locked_on->listref)) evTrigger(ptr->locked_on->listref, EVENT_ENEMY_LOCK_ACHIEVED, "c", source); if (EventDriven(ptr)) evTrigger(ptr, EVENT_LOCK_ACHIEVED, ""); } if (Ship(ptr)) { if (ptr->shipdata->cloak_status == CLOAKING) { if (--ptr->shipdata->time_to_cloak <= 0) { ptr->tagflags |= CLOAKED; ptr->shipdata->cloak_status = CLOAK_ON; MFNotify(ptr->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Cloaking device engaged]"); } } else if (ptr->shipdata->cloak_status == DECLOAKING) { if (--ptr->shipdata->time_to_cloak <= 0) { ptr->shipdata->cloak_status = CLOAK_OFF; MFNotify(ptr->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Cloaking device disengaged]"); } } } } return; } /* * Called for objects which have a tractor beam locked on them. * source is the ship object with the tractor lock. (Only ships have tractors) * target is the ship object locked onto. */ void handle_tractor_target(TAG *source, TAG *target, struct timeval *now) { XYZ pos1, pos2; int eff_tractor_power; float new_warp_speed; object_position(source, &pos1, now, 0); object_position(target, &pos2, now, 0); eff_tractor_power = target->shipdata->alloc_nav - source->shipdata->tractor_power + distance(&pos2, &pos1); if (eff_tractor_power > 0) new_warp_speed = navMaxWarp(target, eff_tractor_power); else new_warp_speed = 0.0; if (new_warp_speed < target->shipdata->warp_speed) { target->shipdata->warp_set_speed = new_warp_speed; MFNotify(target->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Maximum safe warp speed is %3.2f. Warp setting " "reduced]", new_warp_speed); } return; } /* * Called for objects which are tractoring other objects. * source is the object doing the tractoring. * target is the object being tractored. (Not necessarily a ship). */ void handle_tractor_source(TAG *source, TAG *target, struct timeval *now) { int eff_tractor_power, move_power; float new_warp_speed; XYZ xyz, pos1, pos2; SPH sph; float dx, dy, dz; float dist, sum; object_position(source, &pos1, now, 0); object_position(target, &pos2, now, 0); if (Ship(target)) eff_tractor_power = -target->shipdata->alloc_nav + source->shipdata->tractor_power - distance(&pos1, &pos2); else eff_tractor_power = source->shipdata->tractor_power - distance(&pos1, &pos2); move_power = Min(eff_tractor_power, source->shipdata->alloc_nav); if (move_power > 0) new_warp_speed = navMaxWarp(source, move_power); else new_warp_speed = 0.0; if (new_warp_speed < source->shipdata->warp_speed) { source->shipdata->warp_set_speed = new_warp_speed; MFNotify(source->shipdata, -1, CONS_NAV, CONS_ACTIVE, "[Maximum safe warp speed is %3.2f. Warp setting " "reduced]", new_warp_speed); } source->speed = source->shipdata->warp_speed; /* Don't allow objects without the CAN_MOVE flag set to be moved */ if (!CanMove(target)) return; if (source->speed >= 1.0) { sph.bearing = source->heading.bearing; sph.elevation = source->heading.elevation; sph.range = source->speed * source->speed * source->speed; #ifdef ENABLE_ODOMETER if (Ship(target)) F_INT(target->shipdata->rec, odometer) += (sph.range / 10); #endif sph_to_xyz(sph, &xyz); target->dc_pos.x = pos2.x + xyz.x; target->dc_pos.y = pos2.y + xyz.y; target->dc_pos.z = pos2.z + xyz.z; target->dc_time.tv_sec = now->tv_sec; target->dc_time.tv_usec = now->tv_usec; target->moveID = move_turn_id; } if (source->shipdata->tractor_status == TRACTOR_DRAW) { move_power = eff_tractor_power - navWarpCost(source, source->speed); if (move_power > 0) { if (Ship(target)) new_warp_speed = navMaxWarp(target, move_power); else new_warp_speed = source->speed; dist = new_warp_speed * new_warp_speed * new_warp_speed; if (dist > distance(&pos1, &pos2)) { #ifdef ENABLE_ODOMETER if (Ship(target)) F_INT(target->shipdata->rec, odometer) += distance(&pos1, &pos2); #endif target->dc_pos.x = pos1.x; target->dc_pos.y = pos1.y; target->dc_pos.z = pos1.z; target->dc_time.tv_sec = now->tv_sec; target->dc_time.tv_usec = now->tv_usec; } else { dx = pos1.x - pos2.x; dy = pos1.y - pos2.y; dz = pos1.z - pos2.z; sum = sqrt(dx * dx + dy * dy + dz * dz); #ifdef ENABLE_ODOMETER xyz = pos2; #endif target->dc_pos.x = pos2.x + dx / sum * dist; target->dc_pos.y = pos2.y + dy / sum * dist; target->dc_pos.z = pos2.z + dz / sum * dist; target->dc_time.tv_sec = now->tv_sec; target->dc_time.tv_usec = now->tv_usec; #ifdef ENABLE_ODOMETER if (Ship(target)) F_INT(target->shipdata->rec, odometer) += distance(&xyz, &pos2); #endif } target->moveID = move_turn_id; } } return; } /* * Update the timers on any objects in space. */ void update_timers(int space) { TIMER *ti, *tn, *tp; unsigned int cycle; cycle = space_info[space].cycle; tp = NULL; for (tn=ti=space_info[space].timers; tn != NULL; ti=tn) { /* tn is the next timer to check */ tn = ti->next; /* * If the object is flagged to be removed, then remove it. * NOTE: New timers may have been added from another event, * so be very (overly?) careful with the list management here. */ if ((ti->flags & TIMER_REMOVED) || Removed(ti->object)) { if (tp == NULL) { if (space_info[space].timers == ti) { /* Fixup the list head */ space_info[space].timers = tn; free_timer(ti); continue; } tp = space_info[space].timers; while((tp != NULL) && (tp->next != ti)) tp = tp->next; if (tp == NULL) { /* Something really screwball happenned */ log_space("BUG: Attempt to free a timer not listed"); free_timer(ti); continue; } /* tp is the timer preceeding one we're freeing */ } /* tp is a valid timer at this point. Double check next */ if (tp->next == ti) { /* tp->next was correct, so free up the timer */ tp->next = tn; free_timer(ti); continue; } /* This may or may not be a problem. */ log_space("WARNING: Attempt to free a timer with bad tp"); /* For testing - leave it on the list. It should be cleaned * up next timer update - if we ever get to this point. */ continue; } tp = ti; if (ti->expires == cycle) { /* Increment the trigger count */ ti->count ++; /* Emit the event if the timer has expired */ if (ti->id) evTrigger(ti->object, EVENT_TIMER, "si", ti->id, ti->count); else evTrigger(ti->object, EVENT_TIMER, "si", "", ti->count); ti->expires = cycle + ti->interval; if (ti->flags & TIMER_CONTINUOUS) continue; /* If the event is not continuous, free the timer if expired */ ti->events --; if (ti->events <= 0) { ti->flags |= TIMER_REMOVED; } } } } /* * Free a timer, and any associated storage. */ void free_timer(TIMER *timer) { if (timer->id) pse_free(timer->id); pse_free(timer); } void add_timer(TAG *object, int first, int interval, int count, const char *id) { TIMER *timer; timer = pse_malloc(sizeof(TIMER)); /* Setup the appropriate details */ timer->object = object; if (count <= 0) timer->flags = TIMER_CONTINUOUS; else timer->flags = 0; /* Shortest interval is 1 update loop / 1Hz */ if (first < 1) first = 1; if (interval < 1) interval = 1; timer->interval = interval; timer->events = count; timer->expires = space_info[object->space].cycle + first; timer->count = 0; if ((id != NULL) && (*id != '\0')) { timer->id = pse_malloc(1+strlen(id)); strcpy(timer->id, id); } else timer->id = NULL; timer->next = space_info[object->space].timers; space_info[object->space].timers = timer; } /* * Delete the timer for the specified object, or all timers for the object * if id is null. */ void del_timer(TAG *object, const char *id) { TIMER *ti; for (ti=space_info[object->space].timers; ti != NULL; ti=ti->next) { /* If the event is flagged to be removed, kill it */ if ((ti->object != object) || (ti->flags & TIMER_REMOVED)) continue; if ((id == NULL) || (*id == '\0')) { ti->flags |= TIMER_REMOVED; continue; } if (ti->id == NULL) continue; if (strcmp(ti->id, id) == 0) { ti->flags |= TIMER_REMOVED; } } } /* * List the active timers for the specified object. */ void list_timers(TAG *object, void *mud) { TIMER *ti; SSTR *ps; char *buff; buff = pse_malloc(MAX_ATTRIBUTE_LEN); ps = sstr_new(MAX_ATTRIBUTE_LEN); for (ti=space_info[object->space].timers; ti != NULL; ti=ti->next) { /* If the event is flagged to be removed, ignore it */ if ((ti->object != object) || (ti->flags & TIMER_REMOVED)) continue; sprintf(buff, "%s|%d|%d|%d|%d ", ti->id, ti->interval, ti->events, ti->count, ti->expires - space_info[object->space].cycle); sstr_cat(ps, buff, 0); } WriteFunctionBuffer(sstr_str(ps)); pse_free(ps); pse_free(buff); }