#include "scripts.h" #include "actions.h" #include "animation.h" #include "art.h" #include "automap.h" #include "combat.h" #include "core.h" #include "critter.h" #include "debug.h" #include "dialog.h" #include "elevator.h" #include "endgame.h" #include "export.h" #include "game.h" #include "game_dialog.h" #include "game_mouse.h" #include "game_movie.h" #include "memory.h" #include "object.h" #include "party_member.h" #include "platform_compat.h" #include "proto.h" #include "proto_instance.h" #include "queue.h" #include "tile.h" #include "window_manager.h" #include "window_manager_private.h" #include "world_map.h" #include #include #include #include // 0x50D6B8 char _Error_2[] = "Error"; // 0x50D6C0 char byte_50D6C0[] = ""; // Number of lines in scripts.lst // // 0x51C6AC int _num_script_indexes = 0; // 0x51C6B0 int gScriptsEnumerationScriptIndex = 0; // 0x51C6B4 ScriptListExtent* gScriptsEnumerationScriptListExtent = NULL; // 0x51C6B8 int gScriptsEnumerationElevation = 0; // 0x51C6BC bool _scr_SpatialsEnabled = true; // 0x51C6C0 ScriptList gScriptLists[SCRIPT_TYPE_COUNT]; // 0x51C710 const char* gScriptsBasePath = "scripts\\"; // 0x51C714 bool gScriptsEnabled = false; // 0x51C718 int _script_engine_run_critters = 0; // 0x51C71C int _script_engine_game_mode = 0; // Game time in ticks (1/10 second). // // 0x51C720 int gGameTime = 302400; // 0x51C724 const int gGameTimeDaysPerMonth[12] = { 31, // Jan 28, // Feb 31, // Mar 30, // Apr 31, // May 30, // Jun 31, // Jul 31, // Aug 30, // Sep 31, // Oct 30, // Nov 31, // Dec }; // 0x51C758 const char* gScriptProcNames[28] = { "no_p_proc", "start", "spatial_p_proc", "description_p_proc", "pickup_p_proc", "drop_p_proc", "use_p_proc", "use_obj_on_p_proc", "use_skill_on_p_proc", "none_x_bad", "none_x_bad", "talk_p_proc", "critter_p_proc", "combat_p_proc", "damage_p_proc", "map_enter_p_proc", "map_exit_p_proc", "create_p_proc", "destroy_p_proc", "none_x_bad", "none_x_bad", "look_at_p_proc", "timed_event_p_proc", "map_update_p_proc", "push_p_proc", "is_dropping_p_proc", "combat_is_starting_p_proc", "combat_is_over_p_proc", }; // scripts.lst // // 0x51C7C8 ScriptsListEntry* gScriptsListEntries = NULL; // 0x51C7CC int gScriptsListEntriesLength = 0; // 0x51C7D4 int _cur_id = 4; // 0x51C7DC int _count_ = 0; // 0x51C7E0 int _last_time__ = 0; // 0x51C7E4 int _last_light_time = 0; // 0x51C7E8 Object* _scrQueueTestObj = NULL; // 0x51C7EC int _scrQueueTestValue = 0; // 0x51C7F0 char* _err_str = _Error_2; // 0x51C7F4 char* _blank_str = byte_50D6C0; // 0x664954 unsigned int gScriptsRequests; // 0x664958 STRUCT_664980 stru_664958; // 0x664980 STRUCT_664980 stru_664980; // 0x6649A8 int gScriptsRequestedElevatorType; // 0x6649AC int gScriptsRequestedElevatorLevel; // 0x6649B0 int gScriptsRequestedExplosionTile; // 0x6649B4 int gScriptsRequestedExplosionElevation; // 0x6649B8 int gScriptsRequestedExplosionMinDamage; // 0x6649BC int gScriptsRequestedExplosionMaxDamage; // 0x6649C0 Object* gScriptsRequestedDialogWith; // 0x6649C4 Object* gScriptsRequestedLootingBy; // 0x6649C8 Object* gScriptsRequestedLootingFrom; // 0x6649CC Object* gScriptsRequestedStealingBy; // 0x6649D0 Object* gScriptsRequestedStealingFrom; // 0x6649D4 MessageList _script_dialog_msgs[1450]; // scr.msg // // 0x667724 MessageList gScrMessageList; // time string (h:ss) // // 0x66772C char _hour_str[7]; // 0x667748 int _lasttime; // 0x66774C bool _set; // 0x667750 char _tempStr1[20]; // TODO: Make unsigned. // // Returns game time in ticks (1/10 second). // // 0x4A3330 int gameTimeGetTime() { return gGameTime; } // 0x4A3338 void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr) { int year = (gGameTime / GAME_TIME_TICKS_PER_DAY + 24) / 365 + 2241; int month = 6; int day = (gGameTime / GAME_TIME_TICKS_PER_DAY + 24) % 365; while (1) { int daysInMonth = gGameTimeDaysPerMonth[month]; if (day < daysInMonth) { break; } month++; day -= daysInMonth; if (month == 12) { year++; month = 0; } } if (dayPtr != NULL) { *dayPtr = day + 1; } if (monthPtr != NULL) { *monthPtr = month + 1; } if (yearPtr != NULL) { *yearPtr = year; } } // Returns game hour/minute in military format (hhmm). // // Examples: // - 8:00 A.M. -> 800 // - 3:00 P.M. -> 1500 // - 11:59 P.M. -> 2359 // // game_time_hour // 0x4A33C8 int gameTimeGetHour() { return 100 * ((gGameTime / 600) / 60 % 24) + (gGameTime / 600) % 60; } // Returns time string (h:mm) // // 0x4A3420 char* gameTimeGetTimeString() { sprintf(_hour_str, "%d:%02d", (gGameTime / 600) / 60 % 24, (gGameTime / 600) % 60); return _hour_str; } // TODO: Make unsigned. // // 0x4A347C void gameTimeSetTime(int time) { if (time == 0) { time = 1; } gGameTime = time; } // 0x4A34CC void gameTimeAddTicks(int ticks) { gGameTime += ticks; int v1 = 0; unsigned int year = gGameTime / GAME_TIME_TICKS_PER_YEAR; if (year >= 13) { endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_TIMEOUT); _game_user_wants_to_quit = 2; } // FIXME: This condition will never be true. if (v1) { gameTimeEventProcess(NULL, NULL); } } // 0x4A3518 void gameTimeAddSeconds(int seconds) { // NOTE: Uninline. gameTimeAddTicks(seconds * 10); } // 0x4A3570 int gameTimeScheduleUpdateEvent() { int v1 = 10 * (60 * (60 - (gGameTime / 600) % 60 - 1) + 3600 * (24 - (gGameTime / 600) / 60 % 24 - 1) + 60); if (queueAddEvent(v1, NULL, NULL, EVENT_TYPE_GAME_TIME) == -1) { return -1; } if (gMapHeader.name[0] != '\0') { if (queueAddEvent(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) == -1) { return -1; } } return 0; } // 0x4A3620 int gameTimeEventProcess(Object* obj, void* data) { int movie_index; int v4; movie_index = -1; debugPrint("\nQUEUE PROCESS: Midnight!"); if (gameMovieIsPlaying()) { return 0; } objectUnjamAll(); if (!_gdialogActive()) { _scriptsCheckGameEvents(&movie_index, -1); } v4 = _critter_check_rads(gDude); _queue_clear_type(4, 0); gameTimeScheduleUpdateEvent(); if (movie_index != -1) { v4 = 1; } return v4; } // 0x4A3690 int _scriptsCheckGameEvents(int* moviePtr, int window) { int movie = -1; int movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC; bool endgame = false; bool adjustRep = false; int day = gGameTime / GAME_TIME_TICKS_PER_DAY; if (gameGetGlobalVar(GVAR_ENEMY_ARROYO)) { movie = MOVIE_AFAILED; movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_STOP_MUSIC; endgame = true; } else { if (day >= 360 || gameGetGlobalVar(GVAR_FALLOUT_2) >= 3) { movie = MOVIE_ARTIMER4; if (!gameMovieIsSeen(MOVIE_ARTIMER4)) { adjustRep = true; _wmAreaSetVisibleState(CITY_ARROYO, 0, 1); _wmAreaSetVisibleState(CITY_DESTROYED_ARROYO, 1, 1); _wmAreaMarkVisitedState(CITY_DESTROYED_ARROYO, 2); } } else if (day >= 270 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER3; } else if (day >= 180 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER2; } else if (day >= 90 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER1; } } if (movie != -1) { if (gameMovieIsSeen(movie)) { movie = -1; } else { if (window != -1) { windowHide(window); } gameMoviePlay(movie, movieFlags); if (window != -1) { windowUnhide(window); } if (adjustRep) { int rep = gameGetGlobalVar(GVAR_TOWN_REP_ARROYO); gameSetGlobalVar(GVAR_TOWN_REP_ARROYO, rep - 15); } } } if (endgame) { _game_user_wants_to_quit = 2; } else { tileWindowRefresh(); } if (moviePtr != NULL) { *moviePtr = movie; } return 0; } // 0x4A382C int mapUpdateEventProcess(Object* obj, void* data) { scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_UPDATE); _queue_clear_type(EVENT_TYPE_MAP_UPDATE_EVENT, NULL); if (gMapHeader.name[0] == '\0') { return 0; } if (queueAddEvent(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) != -1) { return 0; } return -1; } // new_obj_id // 0x4A386C int scriptsNewObjectId() { Object* ptr; do { _cur_id++; ptr = objectFindFirst(); while (ptr) { if (_cur_id == ptr->id) { break; } ptr = objectFindNext(); } } while (ptr); if (_cur_id >= 18000) { debugPrint("\n ERROR: new_obj_id() !!!! Picked PLAYER ID!!!!"); } _cur_id++; return _cur_id; } // 0x4A390C int scriptGetSid(Program* program) { for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) { ScriptListExtent* extent = gScriptLists[type].head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* script = &(extent->scripts[index]); if (script->program == program) { return script->sid; } } extent = extent->next; } } return -1; } // 0x4A39AC Object* scriptGetSelf(Program* program) { int sid = scriptGetSid(program); Script* script; if (scriptGetScript(sid, &script) == -1) { return NULL; } if (script->owner != NULL) { return script->owner; } if ((sid >> 24) != SCRIPT_TYPE_SPATIAL) { return NULL; } Object* object; int fid = buildFid(6, 3, 0, 0, 0); objectCreateWithFidPid(&object, fid, -1); objectHide(object, NULL); _obj_toggle_flat(object, NULL); object->sid = sid; // NOTE: Redundant, we've already obtained script earlier. Probably // inlining. Script* v1; if (scriptGetScript(sid, &v1) == -1) { // FIXME: this is clearly an error, but I guess it's never reached since // we've already obtained script for given sid earlier. return (Object*)-1; } object->id = scriptsNewObjectId(); v1->field_1C = object->id; v1->owner = object; for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { Script* spatialScript = scriptGetFirstSpatialScript(elevation); while (spatialScript != NULL) { if (spatialScript == script) { objectSetLocation(object, script->sp.built_tile & 0x3FFFFFF, elevation, NULL); return object; } spatialScript = scriptGetNextSpatialScript(); } } return object; } // 0x4A3B0C int scriptSetObjects(int sid, Object* source, Object* target) { Script* script; if (scriptGetScript(sid, &script) == -1) { return -1; } script->source = source; script->target = target; return 0; } // 0x4A3B34 void scriptSetFixedParam(int sid, int value) { Script* script; if (scriptGetScript(sid, &script) != -1) { script->fixedParam = value; } } // 0x4A3B54 int scriptSetActionBeingUsed(int sid, int value) { Script* scr; if (scriptGetScript(sid, &scr) == -1) { return -1; } scr->actionBeingUsed = value; return 0; } // 0x4A3B74 Program* scriptsCreateProgramByName(const char* name) { char path[COMPAT_MAX_PATH]; strcpy(path, _cd_path_base); strcat(path, gScriptsBasePath); strcat(path, name); strcat(path, ".int"); return programCreateByPath(path); } // 0x4A3C2C void _doBkProcesses() { if (!_set) { _lasttime = _get_bk_time(); _set = 1; } int v0 = _get_bk_time(); if (gScriptsEnabled) { _lasttime = v0; // NOTE: There is a loop at 0x4A3C64, consisting of one iteration, going // downwards from 1. for (int index = 0; index < 1; index++) { _updatePrograms(); } } _updateWindows(); if (gScriptsEnabled && _script_engine_run_critters) { // SFALL: Fix to prevent the execution of critter_p_proc and game events // when playing movies. if (!_gdialogActive() && !gameMovieIsPlaying()) { _script_chk_critters(); _script_chk_timed_events(); } } } // 0x4A3CA0 void _script_chk_critters() { if (!_gdialogActive() && !isInCombat()) { ScriptList* scriptList; ScriptListExtent* scriptListExtent; int scriptsCount = 0; scriptList = &(gScriptLists[SCRIPT_TYPE_CRITTER]); scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { scriptsCount += scriptListExtent->length; scriptListExtent = scriptListExtent->next; } _count_ += 1; if (_count_ >= scriptsCount) { _count_ = 0; } if (_count_ < scriptsCount) { int proc = isInCombat() ? SCRIPT_PROC_COMBAT : SCRIPT_PROC_CRITTER; int extentIndex = _count_ / SCRIPT_LIST_EXTENT_SIZE; int scriptIndex = _count_ % SCRIPT_LIST_EXTENT_SIZE; scriptList = &(gScriptLists[SCRIPT_TYPE_CRITTER]); scriptListExtent = scriptList->head; while (scriptListExtent != NULL && extentIndex != 0) { extentIndex -= 1; scriptListExtent = scriptListExtent->next; } if (scriptListExtent != NULL) { Script* script = &(scriptListExtent->scripts[scriptIndex]); scriptExecProc(script->sid, proc); } } } } // TODO: Check. // // 0x4A3D84 void _script_chk_timed_events() { int v0 = _get_bk_time(); int v1 = false; if (!isInCombat()) { v1 = true; } if (_game_state() != 4) { if (getTicksBetween(v0, _last_light_time) >= 30000) { _last_light_time = v0; scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_UPDATE); } } else { v1 = false; } if (getTicksBetween(v0, _last_time__) >= 100) { _last_time__ = v0; if (!isInCombat()) { gGameTime += 1; } v1 = true; } if (v1) { while (!queueIsEmpty()) { int time = gameTimeGetTime(); int v2 = queueGetNextEventTime(); if (time < v2) { break; } queueProcessEvents(); } } } // 0x4A3E30 void _scrSetQueueTestVals(Object* a1, int a2) { _scrQueueTestObj = a1; _scrQueueTestValue = a2; } // 0x4A3E3C int _scrQueueRemoveFixed(Object* obj, void* data) { ScriptEvent* scriptEvent = (ScriptEvent*)data; return obj == _scrQueueTestObj && scriptEvent->field_4 == _scrQueueTestValue; } // 0x4A3E60 int scriptAddTimerEvent(int sid, int delay, int param) { ScriptEvent* scriptEvent = (ScriptEvent*)internal_malloc(sizeof(*scriptEvent)); if (scriptEvent == NULL) { return -1; } scriptEvent->sid = sid; scriptEvent->field_4 = param; Script* script; if (scriptGetScript(sid, &script) == -1) { internal_free(scriptEvent); return -1; } if (queueAddEvent(delay, script->owner, scriptEvent, EVENT_TYPE_SCRIPT) == -1) { internal_free(scriptEvent); return -1; } return 0; } // 0x4A3EDC int scriptEventWrite(File* stream, void* data) { ScriptEvent* scriptEvent = (ScriptEvent*)data; if (fileWriteInt32(stream, scriptEvent->sid) == -1) return -1; if (fileWriteInt32(stream, scriptEvent->field_4) == -1) return -1; return 0; } // 0x4A3F04 int scriptEventRead(File* stream, void** dataPtr) { ScriptEvent* scriptEvent = (ScriptEvent*)internal_malloc(sizeof(*scriptEvent)); if (scriptEvent == NULL) { return -1; } if (fileReadInt32(stream, &(scriptEvent->sid)) == -1) goto err; if (fileReadInt32(stream, &(scriptEvent->field_4)) == -1) goto err; *dataPtr = scriptEvent; return 0; err: // there is a memory leak in original code, free is not called internal_free(scriptEvent); return -1; } // 0x4A3F4C int scriptEventProcess(Object* obj, void* data) { ScriptEvent* scriptEvent = (ScriptEvent*)data; Script* script; if (scriptGetScript(scriptEvent->sid, &script) == -1) { return 0; } script->fixedParam = scriptEvent->field_4; scriptExecProc(scriptEvent->sid, SCRIPT_PROC_TIMED); return 0; } // 0x4A3F80 int scriptsClearPendingRequests() { gScriptsRequests = 0; return 0; } // NOTE: Inlined. // // 0x4A3F90 int _scripts_clear_combat_requests(Script* script) { if ((gScriptsRequests & SCRIPT_REQUEST_COMBAT) != 0 && stru_664958.attacker == script->owner) { gScriptsRequests &= ~(SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); } return 0; } // 0x4A3FB4 int scriptsHandleRequests() { if (gScriptsRequests == 0) { return 0; } if ((gScriptsRequests & SCRIPT_REQUEST_COMBAT) != 0) { if (!_action_explode_running()) { // entering combat gScriptsRequests &= ~(SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); memcpy(&stru_664980, &stru_664958, sizeof(stru_664980)); if ((gScriptsRequests & SCRIPT_REQUEST_0x40) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_0x40; _combat(NULL); } else { _combat(&stru_664980); memset(&stru_664980, 0, sizeof(stru_664980)); } } } if ((gScriptsRequests & SCRIPT_REQUEST_0x02) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_0x02; _wmTownMap(); } if ((gScriptsRequests & SCRIPT_REQUEST_WORLD_MAP) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_WORLD_MAP; _wmWorldMap(); } if ((gScriptsRequests & SCRIPT_REQUEST_ELEVATOR) != 0) { int map = gMapHeader.field_34; int elevation = gScriptsRequestedElevatorLevel; int tile = -1; gScriptsRequests &= ~SCRIPT_REQUEST_ELEVATOR; if (elevatorSelectLevel(gScriptsRequestedElevatorType, &map, &elevation, &tile) != -1) { automapSaveCurrent(); if (map == gMapHeader.field_34) { if (elevation == gElevation) { reg_anim_clear(gDude); objectSetRotation(gDude, ROTATION_SE, 0); _obj_attempt_placement(gDude, tile, elevation, 0); } else { Object* elevatorDoors = objectFindFirstAtElevation(gDude->elevation); while (elevatorDoors != NULL) { int pid = elevatorDoors->pid; if ((pid >> 24) == OBJ_TYPE_SCENERY && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) && tileDistanceBetween(elevatorDoors->tile, gDude->tile) <= 4) { break; } elevatorDoors = objectFindNextAtElevation(); } reg_anim_clear(gDude); objectSetRotation(gDude, ROTATION_SE, 0); _obj_attempt_placement(gDude, tile, elevation, 0); if (elevatorDoors != NULL) { objectSetFrame(elevatorDoors, 0, NULL); objectSetLocation(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; elevatorDoors->data.scenery.door.openFlags &= ~0x01; _obj_rebuild_all_light(); } else { debugPrint("\nWarning: Elevator: Couldn't find old elevator doors!"); } } } else { Object* elevatorDoors = objectFindFirstAtElevation(gDude->elevation); while (elevatorDoors != NULL) { int pid = elevatorDoors->pid; if ((pid >> 24) == OBJ_TYPE_SCENERY && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) && tileDistanceBetween(elevatorDoors->tile, gDude->tile) <= 4) { break; } elevatorDoors = objectFindNextAtElevation(); } if (elevatorDoors != NULL) { objectSetFrame(elevatorDoors, 0, NULL); objectSetLocation(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; elevatorDoors->data.scenery.door.openFlags &= ~0x01; _obj_rebuild_all_light(); } else { debugPrint("\nWarning: Elevator: Couldn't find old elevator doors!"); } MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = map; transition.elevation = elevation; transition.tile = tile; transition.rotation = ROTATION_SE; mapSetTransition(&transition); } } } if ((gScriptsRequests & SCRIPT_REQUEST_EXPLOSION) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_EXPLOSION; actionExplode(gScriptsRequestedExplosionTile, gScriptsRequestedExplosionElevation, gScriptsRequestedExplosionMinDamage, gScriptsRequestedExplosionMaxDamage, NULL, 1); } if ((gScriptsRequests & SCRIPT_REQUEST_DIALOG) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_DIALOG; gameDialogEnter(gScriptsRequestedDialogWith, 0); } if ((gScriptsRequests & SCRIPT_REQUEST_ENDGAME) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_ENDGAME; endgamePlaySlideshow(); endgamePlayMovie(); } if ((gScriptsRequests & SCRIPT_REQUEST_LOOTING) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_LOOTING; inventoryOpenLooting(gScriptsRequestedLootingBy, gScriptsRequestedLootingFrom); } if ((gScriptsRequests & SCRIPT_REQUEST_STEALING) != 0) { gScriptsRequests &= ~SCRIPT_REQUEST_STEALING; inventoryOpenStealing(gScriptsRequestedStealingBy, gScriptsRequestedStealingFrom); } return 0; } // 0x4A43A0 int _scripts_check_state_in_combat() { if ((gScriptsRequests & SCRIPT_REQUEST_ELEVATOR) != 0) { int map = gMapHeader.field_34; int elevation = gScriptsRequestedElevatorLevel; int tile = -1; if (elevatorSelectLevel(gScriptsRequestedElevatorType, &map, &elevation, &tile) != -1) { automapSaveCurrent(); if (map == gMapHeader.field_34) { if (elevation == gElevation) { reg_anim_clear(gDude); objectSetRotation(gDude, ROTATION_SE, 0); _obj_attempt_placement(gDude, tile, elevation, 0); } else { Object* elevatorDoors = objectFindFirstAtElevation(gDude->elevation); while (elevatorDoors != NULL) { int pid = elevatorDoors->pid; if ((pid >> 24) == OBJ_TYPE_SCENERY && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) && tileDistanceBetween(elevatorDoors->tile, gDude->tile) <= 4) { break; } elevatorDoors = objectFindNextAtElevation(); } reg_anim_clear(gDude); objectSetRotation(gDude, ROTATION_SE, 0); _obj_attempt_placement(gDude, tile, elevation, 0); if (elevatorDoors != NULL) { objectSetFrame(elevatorDoors, 0, NULL); objectSetLocation(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; elevatorDoors->data.scenery.door.openFlags &= ~0x01; _obj_rebuild_all_light(); } else { debugPrint("\nWarning: Elevator: Couldn't find old elevator doors!"); } } } else { MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = map; transition.elevation = elevation; transition.tile = tile; transition.rotation = ROTATION_SE; mapSetTransition(&transition); } } } if ((gScriptsRequests & SCRIPT_REQUEST_LOOTING) != 0) { inventoryOpenLooting(gScriptsRequestedLootingBy, gScriptsRequestedLootingFrom); } // NOTE: Uninline. scriptsClearPendingRequests(); return 0; } // 0x4A457C int scriptsRequestCombat(STRUCT_664980* a1) { if ((gScriptsRequests & SCRIPT_REQUEST_0x0400) != 0) { return -1; } if (a1) { memcpy(&stru_664958, a1, sizeof(stru_664958)); } else { gScriptsRequests |= SCRIPT_REQUEST_0x40; } gScriptsRequests |= SCRIPT_REQUEST_COMBAT; return 0; } // Likely related to random encounter, ala scriptsRequestRandomEncounter RELEASE // // 0x4A45D4 void _scripts_request_combat_locked(STRUCT_664980* a1) { if (a1 != NULL) { memcpy(&stru_664958, a1, sizeof(stru_664958)); } else { gScriptsRequests |= SCRIPT_REQUEST_0x40; } gScriptsRequests |= (SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT); } // request_world_map() // 0x4A4644 void scriptsRequestWorldMap() { if (isInCombat()) { _game_user_wants_to_quit = 1; } gScriptsRequests |= SCRIPT_REQUEST_WORLD_MAP; } // scripts_request_elevator // 0x4A466C int scriptsRequestElevator(Object* a1, int a2) { int elevatorType = a2; int elevatorLevel = gElevation; int tile = a1->tile; if (tile == -1) { debugPrint("\nError: scripts_request_elevator! Bad tile num"); return -1; } // TODO: What the hell is this? tile -= 1005; Object* obj; for (int y = -5; y < 5; y++) { for (int x = -5; x < 5; x++) { obj = objectFindFirstAtElevation(a1->elevation); while (obj != NULL) { if (tile == obj->tile && obj->pid == PROTO_ID_0x200050D) { break; } obj = objectFindNextAtElevation(); } if (obj != NULL) { break; } tile += 1; } if (obj != NULL) { break; } tile += 190; } if (obj != NULL) { elevatorType = obj->data.scenery.elevator.field_0; elevatorLevel = obj->data.scenery.elevator.field_4; } if (elevatorType == -1) { return -1; } gScriptsRequests |= SCRIPT_REQUEST_ELEVATOR; gScriptsRequestedElevatorType = elevatorType; gScriptsRequestedElevatorLevel = elevatorLevel; return 0; } // 0x4A4730 int scriptsRequestExplosion(int tile, int elevation, int minDamage, int maxDamage) { gScriptsRequests |= SCRIPT_REQUEST_EXPLOSION; gScriptsRequestedExplosionTile = tile; gScriptsRequestedExplosionElevation = elevation; gScriptsRequestedExplosionMinDamage = minDamage; gScriptsRequestedExplosionMaxDamage = maxDamage; return 0; } // 0x4A4754 void scriptsRequestDialog(Object* obj) { gScriptsRequestedDialogWith = obj; gScriptsRequests |= SCRIPT_REQUEST_DIALOG; } // 0x4A4770 void scriptsRequestEndgame() { gScriptsRequests |= SCRIPT_REQUEST_ENDGAME; } // 0x4A477C int scriptsRequestLooting(Object* a1, Object* a2) { gScriptsRequestedLootingBy = a1; gScriptsRequestedLootingFrom = a2; gScriptsRequests |= SCRIPT_REQUEST_LOOTING; return 0; } // 0x4A479C int scriptsRequestStealing(Object* a1, Object* a2) { gScriptsRequestedStealingBy = a1; gScriptsRequestedStealingFrom = a2; gScriptsRequests |= SCRIPT_REQUEST_STEALING; return 0; } // exec_script_proc // 0x4A4810 int scriptExecProc(int sid, int proc) { if (!gScriptsEnabled) { return -1; } Script* script; if (scriptGetScript(sid, &script) == -1) { return -1; } script->scriptOverrides = 0; bool programLoaded = false; if ((script->flags & SCRIPT_FLAG_0x01) == 0) { clock(); char name[16]; if (scriptsGetFileName(script->field_14 & 0xFFFFFF, name) == -1) { return -1; } char* pch = strchr(name, '.'); if (pch != NULL) { *pch = '\0'; } script->program = scriptsCreateProgramByName(name); if (script->program == NULL) { debugPrint("\nError: exec_script_proc: script load failed!"); return -1; } programLoaded = true; script->flags |= SCRIPT_FLAG_0x01; } Program* program = script->program; if (program == NULL) { return -1; } if ((program->flags & 0x0124) != 0) { return 0; } int v9 = script->procs[proc]; if (v9 == 0) { v9 = 1; } if (v9 == -1) { return -1; } if (script->target == NULL) { script->target = script->owner; } script->flags |= SCRIPT_FLAG_0x04; if (programLoaded) { scriptLocateProcs(script); v9 = script->procs[proc]; if (v9 == 0) { v9 = 1; } script->action = 0; programListNodeCreate(program); _interpret(program, -1); } script->action = proc; _executeProcedure(program, v9); script->source = NULL; return 0; } // Locate built-in procs for given script. // // 0x4A49D0 int scriptLocateProcs(Script* script) { for (int proc = 0; proc < SCRIPT_PROC_COUNT; proc++) { int index = programFindProcedure(script->program, gScriptProcNames[proc]); if (index == -1) { index = SCRIPT_PROC_NO_PROC; } script->procs[proc] = index; } return 0; } // 0x4A4A08 bool scriptHasProc(int sid, int proc) { Script* scr; if (scriptGetScript(sid, &scr) == -1) { return 0; } return scr->procs[proc] != SCRIPT_PROC_NO_PROC; } // 0x4A4D50 int scriptsLoadScriptsList() { char path[COMPAT_MAX_PATH]; sprintf(path, "%s%s", "scripts\\", "scripts.lst"); File* stream = fileOpen(path, "rt"); if (stream == NULL) { return -1; } char string[260]; while (fileReadString(string, 260, stream)) { gScriptsListEntriesLength++; ScriptsListEntry* entries = (ScriptsListEntry*)internal_realloc(gScriptsListEntries, sizeof(*entries) * gScriptsListEntriesLength); if (entries == NULL) { return -1; } gScriptsListEntries = entries; ScriptsListEntry* entry = &(entries[gScriptsListEntriesLength - 1]); entry->local_vars_num = 0; char* substr = strstr(string, ".int"); if (substr != NULL) { int length = substr - string; if (length > 13) { return -1; } strncpy(entry->name, string, 13); entry->name[length] = '\0'; } if (strstr(string, "#") != NULL) { substr = strstr(string, "local_vars="); if (substr != NULL) { entry->local_vars_num = atoi(substr + 11); } } } fileClose(stream); return 0; } // NOTE: Inlined. // // 0x4A4EFC int scriptsFreeScriptsList() { if (gScriptsListEntries != NULL) { internal_free(gScriptsListEntries); gScriptsListEntries = NULL; } gScriptsListEntriesLength = 0; return 0; } // 0x4A4F28 int _scr_find_str_run_info(int scriptIndex, int* a2, int sid) { Script* script; if (scriptGetScript(sid, &script) == -1) { return -1; } script->localVarsCount = gScriptsListEntries[scriptIndex].local_vars_num; return 0; } // 0x4A4F68 int scriptsGetFileName(int scriptIndex, char* name) { sprintf(name, "%s.int", gScriptsListEntries[scriptIndex].name); return 0; } // scr_set_dude_script // 0x4A4F90 int scriptsSetDudeScript() { if (scriptsClearDudeScript() == -1) { return -1; } if (gDude == NULL) { debugPrint("Error in scr_set_dude_script: obj_dude uninitialized!"); return -1; } Proto* proto; if (protoGetProto(0x1000000, &proto) == -1) { debugPrint("Error in scr_set_dude_script: can't find obj_dude proto!"); return -1; } proto->critter.sid = 0x4000000; _obj_new_sid(gDude, &(gDude->sid)); Script* script; if (scriptGetScript(gDude->sid, &script) == -1) { debugPrint("Error in scr_set_dude_script: can't find obj_dude script!"); return -1; } script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); return 0; } // scr_clear_dude_script // 0x4A5044 int scriptsClearDudeScript() { if (gDude == NULL) { debugPrint("\nError in scr_clear_dude_script: obj_dude uninitialized!"); return -1; } if (gDude->sid != -1) { Script* script; if (scriptGetScript(gDude->sid, &script) != -1) { script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); } scriptRemove(gDude->sid); gDude->sid = -1; } return 0; } // scr_init // 0x4A50A8 int scriptsInit() { if (!messageListInit(&gScrMessageList)) { return -1; } for (int index = 0; index < 1450; index++) { if (!messageListInit(&(_script_dialog_msgs[index]))) { return -1; } } _scr_remove_all(); _interpretOutputFunc(_win_debug); interpreterRegisterOpcodeHandlers(); _scr_header_load(); // NOTE: Uninline. scriptsClearPendingRequests(); _partyMemberClear(); if (scriptsLoadScriptsList() == -1) { return -1; } return 0; } // 0x4A5120 int _scr_reset() { _scr_remove_all(); // NOTE: Uninline. scriptsClearPendingRequests(); _partyMemberClear(); return 0; } // 0x4A5138 int _scr_game_init() { int i; char path[COMPAT_MAX_PATH]; if (!messageListInit(&gScrMessageList)) { debugPrint("\nError initing script message file!"); return -1; } for (i = 0; i < 1450; i++) { if (!messageListInit(&(_script_dialog_msgs[i]))) { debugPrint("\nERROR IN SCRIPT_DIALOG_MSGS!"); return -1; } } sprintf(path, "%s%s", asc_5186C8, "script.msg"); if (!messageListLoad(&gScrMessageList, path)) { debugPrint("\nError loading script message file!"); return -1; } gScriptsEnabled = true; _script_engine_game_mode = 1; gGameTime = 1; gameTimeSetTime(302400); tickersAdd(_doBkProcesses); if (scriptsSetDudeScript() == -1) { return -1; } _scr_SpatialsEnabled = true; // NOTE: Uninline. scriptsClearPendingRequests(); return 0; } // 0x4A5240 int scriptsReset() { debugPrint("\nScripts: [Game Reset]"); _scr_game_exit(); _scr_game_init(); _partyMemberClear(); _scr_remove_all_force(); return scriptsSetDudeScript(); } // 0x4A5274 int scriptsExit() { gScriptsEnabled = false; _script_engine_run_critters = 0; if (!messageListFree(&gScrMessageList)) { debugPrint("\nError exiting script message file!"); return -1; } _scr_remove_all(); _scr_remove_all_force(); _interpretClose(); programListFree(); // NOTE: Uninline. scriptsClearPendingRequests(); // NOTE: Uninline. scriptsFreeScriptsList(); return 0; } // scr_message_free // 0x4A52F4 int _scr_message_free() { for (int index = 0; index < 1450; index++) { MessageList* messageList = &(_script_dialog_msgs[index]); if (messageList->entries_num != 0) { if (!messageListFree(messageList)) { debugPrint("\nERROR in scr_message_free!"); return -1; } if (!messageListInit(messageList)) { debugPrint("\nERROR in scr_message_free!"); return -1; } } } return 0; } // 0x4A535C int _scr_game_exit() { _script_engine_game_mode = 0; gScriptsEnabled = false; _script_engine_run_critters = 0; _scr_message_free(); _scr_remove_all(); programListFree(); tickersRemove(_doBkProcesses); messageListFree(&gScrMessageList); if (scriptsClearDudeScript() == -1) { return -1; } // NOTE: Uninline. scriptsClearPendingRequests(); return 0; } // scr_enable // 0x4A53A8 int scriptsEnable() { if (!_script_engine_game_mode) { return -1; } _script_engine_run_critters = 1; gScriptsEnabled = true; return 0; } // scr_disable // 0x4A53D0 int scriptsDisable() { gScriptsEnabled = false; return 0; } // 0x4A53E0 void _scr_enable_critters() { _script_engine_run_critters = 1; } // 0x4A53F0 void _scr_disable_critters() { _script_engine_run_critters = 0; } // 0x4A5400 int scriptsSaveGameGlobalVars(File* stream) { return fileWriteInt32List(stream, gGameGlobalVars, gGameGlobalVarsLength); } // 0x4A5424 int scriptsLoadGameGlobalVars(File* stream) { return fileReadInt32List(stream, gGameGlobalVars, gGameGlobalVarsLength); } // NOTE: For unknown reason save game files contains two identical sets of game // global variables (saved with [scriptsSaveGameGlobalVars]). The first set is // read with [scriptsLoadGameGlobalVars], the second set is simply thrown away // using this function. // // 0x4A5448 int scriptsSkipGameGlobalVars(File* stream) { int* vars = (int*)internal_malloc(sizeof(*vars) * gGameGlobalVarsLength); if (vars == NULL) { return -1; } if (fileReadInt32List(stream, vars, gGameGlobalVarsLength) == -1) { // FIXME: Leaks vars. return -1; } internal_free(vars); return 0; } // 0x4A5490 int _scr_header_load() { _num_script_indexes = 0; char path[COMPAT_MAX_PATH]; strcpy(path, _cd_path_base); strcat(path, gScriptsBasePath); strcat(path, "scripts.lst"); File* stream = fileOpen(path, "rt"); if (stream == NULL) { return -1; } while (1) { char ch = fileReadChar(stream); if (ch == -1) { break; } if (ch == '\n') { _num_script_indexes++; } } _num_script_indexes++; fileClose(stream); for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(gScriptLists[scriptType]); scriptList->head = NULL; scriptList->tail = NULL; scriptList->length = 0; scriptList->nextScriptId = 0; } return 0; } // 0x4A5590 int scriptWrite(Script* scr, File* stream) { if (fileWriteInt32(stream, scr->sid) == -1) return -1; if (fileWriteInt32(stream, scr->field_4) == -1) return -1; switch (scr->sid >> 24) { case SCRIPT_TYPE_SPATIAL: if (fileWriteInt32(stream, scr->sp.built_tile) == -1) return -1; if (fileWriteInt32(stream, scr->sp.radius) == -1) return -1; break; case SCRIPT_TYPE_TIMED: if (fileWriteInt32(stream, scr->tm.time) == -1) return -1; break; } if (fileWriteInt32(stream, scr->flags) == -1) return -1; if (fileWriteInt32(stream, scr->field_14) == -1) return -1; // NOTE: Original code writes `scr->program` pointer which is meaningless. if (fileWriteInt32(stream, 0) == -1) return -1; if (fileWriteInt32(stream, scr->field_1C) == -1) return -1; if (fileWriteInt32(stream, scr->localVarsOffset) == -1) return -1; if (fileWriteInt32(stream, scr->localVarsCount) == -1) return -1; if (fileWriteInt32(stream, scr->field_28) == -1) return -1; if (fileWriteInt32(stream, scr->action) == -1) return -1; if (fileWriteInt32(stream, scr->fixedParam) == -1) return -1; if (fileWriteInt32(stream, scr->actionBeingUsed) == -1) return -1; if (fileWriteInt32(stream, scr->scriptOverrides) == -1) return -1; if (fileWriteInt32(stream, scr->field_48) == -1) return -1; if (fileWriteInt32(stream, scr->howMuch) == -1) return -1; if (fileWriteInt32(stream, scr->field_50) == -1) return -1; return 0; } // 0x4A5704 int scriptListExtentWrite(ScriptListExtent* a1, File* stream) { for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) { Script* script = &(a1->scripts[index]); if (scriptWrite(script, stream) != 0) { return -1; } } if (fileWriteInt32(stream, a1->length) != 0) { return -1; } // NOTE: Original code writes `a1->next` pointer which is meaningless. if (fileWriteInt32(stream, 0) != 0) { return -1; } return 0; } // 0x4A5768 int scriptSaveAll(File* stream) { for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(gScriptLists[scriptType]); int scriptCount = scriptList->length * SCRIPT_LIST_EXTENT_SIZE; if (scriptList->tail != NULL) { scriptCount += scriptList->tail->length - SCRIPT_LIST_EXTENT_SIZE; } ScriptListExtent* scriptExtent = scriptList->head; ScriptListExtent* lastScriptExtent = NULL; while (scriptExtent != NULL) { for (int index = 0; index < scriptExtent->length; index++) { Script* script = &(scriptExtent->scripts[index]); lastScriptExtent = scriptList->tail; if ((script->flags & SCRIPT_FLAG_0x08) != 0) { scriptCount--; int backwardsIndex = lastScriptExtent->length - 1; if (lastScriptExtent == scriptExtent && backwardsIndex <= index) { break; } while (lastScriptExtent != scriptExtent || backwardsIndex > index) { Script* backwardsScript = &(lastScriptExtent->scripts[backwardsIndex]); if ((backwardsScript->flags & SCRIPT_FLAG_0x08) == 0) { break; } backwardsIndex--; if (backwardsIndex < 0) { ScriptListExtent* previousScriptExtent = scriptList->head; while (previousScriptExtent->next != lastScriptExtent) { previousScriptExtent = previousScriptExtent->next; } lastScriptExtent = previousScriptExtent; backwardsIndex = lastScriptExtent->length - 1; } } if (lastScriptExtent != scriptExtent || backwardsIndex > index) { Script temp; memcpy(&temp, script, sizeof(Script)); memcpy(script, &(lastScriptExtent->scripts[backwardsIndex]), sizeof(Script)); memcpy(&(lastScriptExtent->scripts[backwardsIndex]), &temp, sizeof(Script)); scriptCount++; } } } scriptExtent = scriptExtent->next; } if (fileWriteInt32(stream, scriptCount) == -1) { return -1; } if (scriptCount > 0) { ScriptListExtent* scriptExtent = scriptList->head; while (scriptExtent != lastScriptExtent) { if (scriptListExtentWrite(scriptExtent, stream) == -1) { return -1; } scriptExtent = scriptExtent->next; } if (lastScriptExtent != NULL) { int index; for (index = 0; index < lastScriptExtent->length; index++) { Script* script = &(lastScriptExtent->scripts[index]); if ((script->flags & SCRIPT_FLAG_0x08) != 0) { break; } } if (index > 0) { int length = lastScriptExtent->length; lastScriptExtent->length = index; if (scriptListExtentWrite(lastScriptExtent, stream) == -1) { return -1; } lastScriptExtent->length = length; } } } } return 0; } // 0x4A5A1C int scriptRead(Script* scr, File* stream) { int prg; if (fileReadInt32(stream, &(scr->sid)) == -1) return -1; if (fileReadInt32(stream, &(scr->field_4)) == -1) return -1; switch (scr->sid >> 24) { case SCRIPT_TYPE_SPATIAL: if (fileReadInt32(stream, &(scr->sp.built_tile)) == -1) return -1; if (fileReadInt32(stream, &(scr->sp.radius)) == -1) return -1; break; case SCRIPT_TYPE_TIMED: if (fileReadInt32(stream, &(scr->tm.time)) == -1) return -1; break; } if (fileReadInt32(stream, &(scr->flags)) == -1) return -1; if (fileReadInt32(stream, &(scr->field_14)) == -1) return -1; if (fileReadInt32(stream, &(prg)) == -1) return -1; if (fileReadInt32(stream, &(scr->field_1C)) == -1) return -1; if (fileReadInt32(stream, &(scr->localVarsOffset)) == -1) return -1; if (fileReadInt32(stream, &(scr->localVarsCount)) == -1) return -1; if (fileReadInt32(stream, &(scr->field_28)) == -1) return -1; if (fileReadInt32(stream, &(scr->action)) == -1) return -1; if (fileReadInt32(stream, &(scr->fixedParam)) == -1) return -1; if (fileReadInt32(stream, &(scr->actionBeingUsed)) == -1) return -1; if (fileReadInt32(stream, &(scr->scriptOverrides)) == -1) return -1; if (fileReadInt32(stream, &(scr->field_48)) == -1) return -1; if (fileReadInt32(stream, &(scr->howMuch)) == -1) return -1; if (fileReadInt32(stream, &(scr->field_50)) == -1) return -1; scr->program = NULL; scr->owner = NULL; scr->source = NULL; scr->target = NULL; for (int index = 0; index < SCRIPT_PROC_COUNT; index++) { scr->procs[index] = 0; } if (!(gMapHeader.flags & 1)) { scr->localVarsCount = 0; } return 0; } // 0x4A5BE8 int scriptListExtentRead(ScriptListExtent* scriptExtent, File* stream) { for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) { Script* scr = &(scriptExtent->scripts[index]); if (scriptRead(scr, stream) != 0) { return -1; } } if (fileReadInt32(stream, &(scriptExtent->length)) != 0) { return -1; } int next; if (fileReadInt32(stream, &(next)) != 0) { return -1; } return 0; } // 0x4A5C50 int scriptLoadAll(File* stream) { for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) { ScriptList* scriptList = &(gScriptLists[index]); int scriptsCount = 0; if (fileReadInt32(stream, &scriptsCount) == -1) { return -1; } if (scriptsCount != 0) { scriptList->length = scriptsCount / 16; if (scriptsCount % 16 != 0) { scriptList->length++; } ScriptListExtent* extent = (ScriptListExtent*)internal_malloc(sizeof(*extent)); scriptList->head = extent; scriptList->tail = extent; if (extent == NULL) { return -1; } if (scriptListExtentRead(extent, stream) != 0) { return -1; } for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { Script* script = &(extent->scripts[scriptIndex]); script->owner = NULL; script->source = NULL; script->target = NULL; script->program = NULL; script->flags &= ~SCRIPT_FLAG_0x01; } extent->next = NULL; ScriptListExtent* prevExtent = extent; for (int extentIndex = 1; extentIndex < scriptList->length; extentIndex++) { ScriptListExtent* extent = (ScriptListExtent*)internal_malloc(sizeof(*extent)); if (extent == NULL) { return -1; } if (scriptListExtentRead(extent, stream) != 0) { return -1; } for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { Script* script = &(extent->scripts[scriptIndex]); script->owner = NULL; script->source = NULL; script->target = NULL; script->program = NULL; script->flags &= ~SCRIPT_FLAG_0x01; } prevExtent->next = extent; extent->next = NULL; prevExtent = extent; } scriptList->tail = prevExtent; } else { scriptList->head = NULL; scriptList->tail = NULL; scriptList->length = 0; } } return 0; } // scr_ptr // 0x4A5E34 int scriptGetScript(int sid, Script** scriptPtr) { *scriptPtr = NULL; if (sid == -1) { return -1; } if (sid == 0xCCCCCCCC) { debugPrint("\nERROR: scr_ptr called with UN-SET id #!!!!"); return -1; } ScriptList* scriptList = &(gScriptLists[sid >> 24]); ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { for (int index = 0; index < scriptListExtent->length; index++) { Script* script = &(scriptListExtent->scripts[index]); if (script->sid == sid) { *scriptPtr = script; return 0; } } scriptListExtent = scriptListExtent->next; } return -1; } // 0x4A5ED8 int scriptGetNewId(int scriptType) { int scriptId = gScriptLists[scriptType].nextScriptId++; int v1 = scriptType << 24; while (scriptId < 32000) { Script* script; if (scriptGetScript(v1 | scriptId, &script) == -1) { break; } scriptId++; } return scriptId; } // 0x4A5F28 int scriptAdd(int* sidPtr, int scriptType) { ScriptList* scriptList = &(gScriptLists[scriptType]); ScriptListExtent* scriptListExtent = scriptList->tail; if (scriptList->head != NULL) { // There is at least one extent available, which means tail is also set. if (scriptListExtent->length == SCRIPT_LIST_EXTENT_SIZE) { ScriptListExtent* newExtent = scriptListExtent->next = (ScriptListExtent*)internal_malloc(sizeof(*newExtent)); if (newExtent == NULL) { return -1; } newExtent->length = 0; newExtent->next = NULL; scriptList->tail = newExtent; scriptList->length++; scriptListExtent = newExtent; } } else { // Script head scriptListExtent = (ScriptListExtent*)internal_malloc(sizeof(ScriptListExtent)); if (scriptListExtent == NULL) { return -1; } scriptListExtent->length = 0; scriptListExtent->next = NULL; scriptList->head = scriptListExtent; scriptList->tail = scriptListExtent; scriptList->length = 1; } int sid = scriptGetNewId(scriptType) | (scriptType << 24); *sidPtr = sid; Script* scr = &(scriptListExtent->scripts[scriptListExtent->length]); scr->sid = sid; scr->sp.built_tile = -1; scr->sp.radius = -1; scr->flags = 0; scr->field_14 = -1; scr->program = 0; scr->localVarsOffset = -1; scr->localVarsCount = 0; scr->field_28 = 0; scr->action = 0; scr->fixedParam = 0; scr->owner = 0; scr->source = 0; scr->target = 0; scr->actionBeingUsed = -1; scr->scriptOverrides = 0; scr->field_48 = 0; scr->howMuch = 0; scr->field_50 = 0; for (int index = 0; index < SCRIPT_PROC_COUNT; index++) { scr->procs[index] = SCRIPT_PROC_NO_PROC; } scriptListExtent->length++; return 0; } // scr_remove_local_vars // 0x4A60D4 int scriptsRemoveLocalVars(Script* script) { if (script == NULL) { return -1; } if (script->localVarsCount != 0) { int oldMapLocalVarsCount = gMapLocalVarsLength; if (oldMapLocalVarsCount > 0 && script->localVarsOffset >= 0) { gMapLocalVarsLength -= script->localVarsCount; if (oldMapLocalVarsCount - script->localVarsCount != script->localVarsOffset && script->localVarsOffset != -1) { memmove(gMapLocalVars + script->localVarsOffset, gMapLocalVars + (script->localVarsOffset + script->localVarsCount), sizeof(*gMapLocalVars) * (oldMapLocalVarsCount - script->localVarsCount - script->localVarsOffset)); gMapLocalVars = (int*)internal_realloc(gMapLocalVars, sizeof(*gMapLocalVars) * gMapLocalVarsLength); if (gMapLocalVars == NULL) { debugPrint("\nError in mem_realloc in scr_remove_local_vars!\n"); } for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) { ScriptList* scriptList = &(gScriptLists[index]); ScriptListExtent* extent = scriptList->head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* other = &(extent->scripts[index]); if (other->localVarsOffset > script->localVarsOffset) { other->localVarsOffset -= script->localVarsCount; } } extent = extent->next; } } } } } return 0; } // scr_remove // 0x4A61D4 int scriptRemove(int sid) { if (sid == -1) { return -1; } ScriptList* scriptList = &(gScriptLists[sid >> 24]); ScriptListExtent* scriptListExtent = scriptList->head; int index; while (scriptListExtent != NULL) { for (index = 0; index < scriptListExtent->length; index++) { Script* script = &(scriptListExtent->scripts[index]); if (script->sid == sid) { break; } } if (index < scriptListExtent->length) { break; } scriptListExtent = scriptListExtent->next; } if (scriptListExtent == NULL) { return -1; } Script* script = &(scriptListExtent->scripts[index]); if ((script->flags & SCRIPT_FLAG_0x02) != 0) { if (script->program != NULL) { script->program = NULL; } } if ((script->flags & SCRIPT_FLAG_0x10) == 0) { // NOTE: Uninline. _scripts_clear_combat_requests(script); if (scriptsRemoveLocalVars(script) == -1) { debugPrint("\nERROR Removing local vars on scr_remove!!\n"); } if (queueRemoveEventsByType(script->owner, EVENT_TYPE_SCRIPT) == -1) { debugPrint("\nERROR Removing Timed Events on scr_remove!!\n"); } if (scriptListExtent == scriptList->tail && index + 1 == scriptListExtent->length) { // Removing last script in tail extent scriptListExtent->length -= 1; if (scriptListExtent->length == 0) { scriptList->length--; internal_free(scriptListExtent); if (scriptList->length != 0) { ScriptListExtent* v13 = scriptList->head; while (scriptList->tail != v13->next) { v13 = v13->next; } v13->next = NULL; scriptList->tail = v13; } else { scriptList->head = NULL; scriptList->tail = NULL; } } } else { // Relocate last script from tail extent into this script's slot. memcpy(&(scriptListExtent->scripts[index]), &(scriptList->tail->scripts[scriptList->tail->length - 1]), sizeof(Script)); // Decrement number of scripts in tail extent. scriptList->tail->length -= 1; // Check to see if this extent became empty. if (scriptList->tail->length == 0) { scriptList->length -= 1; // Find previous extent that is about to become a new tail for // this script list. ScriptListExtent* prev = scriptList->head; while (prev->next != scriptList->tail) { prev = prev->next; } prev->next = NULL; internal_free(scriptList->tail); scriptList->tail = prev; } } } return 0; } // 0x4A63E0 int _scr_remove_all() { _queue_clear_type(EVENT_TYPE_SCRIPT, NULL); _scr_message_free(); for (int scrType = 0; scrType < SCRIPT_TYPE_COUNT; scrType++) { ScriptList* scriptList = &(gScriptLists[scrType]); // TODO: Super odd way to remove scripts. The problem is that [scrRemove] // does relocate scripts between extents, so current extent may become // empty. In addition there is a 0x10 flag on the script that is not // removed. Find a way to refactor this. ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { ScriptListExtent* next = NULL; for (int scriptIndex = 0; scriptIndex < scriptListExtent->length;) { Script* script = &(scriptListExtent->scripts[scriptIndex]); if ((script->flags & SCRIPT_FLAG_0x10) != 0) { scriptIndex++; } else { if (scriptIndex != 0 || scriptListExtent->length != 1) { scriptRemove(script->sid); } else { next = scriptListExtent->next; scriptRemove(script->sid); } } } scriptListExtent = next; } } gScriptsEnumerationScriptIndex = 0; gScriptsEnumerationScriptListExtent = NULL; gScriptsEnumerationElevation = 0; gMapSid = -1; programListFree(); _exportClearAllVariables(); return 0; } // 0x4A64A8 int _scr_remove_all_force() { _queue_clear_type(EVENT_TYPE_SCRIPT, NULL); _scr_message_free(); for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) { ScriptList* scriptList = &(gScriptLists[type]); ScriptListExtent* extent = scriptList->head; while (extent != NULL) { ScriptListExtent* next = extent->next; internal_free(extent); extent = next; } scriptList->head = NULL; scriptList->tail = NULL; scriptList->length = 0; } gScriptsEnumerationScriptIndex = 0; gScriptsEnumerationScriptListExtent = 0; gScriptsEnumerationElevation = 0; gMapSid = -1; programListFree(); _exportClearAllVariables(); return 0; } // 0x4A6524 Script* scriptGetFirstSpatialScript(int elevation) { gScriptsEnumerationElevation = elevation; gScriptsEnumerationScriptIndex = 0; gScriptsEnumerationScriptListExtent = gScriptLists[SCRIPT_TYPE_SPATIAL].head; if (gScriptsEnumerationScriptListExtent == NULL) { return NULL; } Script* script = &(gScriptsEnumerationScriptListExtent->scripts[0]); if ((script->flags & SCRIPT_FLAG_0x02) != 0 || ((script->sp.built_tile & 0xE0000000) >> 29) != elevation) { script = scriptGetNextSpatialScript(); } return script; } // 0x4A6564 Script* scriptGetNextSpatialScript() { ScriptListExtent* scriptListExtent = gScriptsEnumerationScriptListExtent; int scriptIndex = gScriptsEnumerationScriptIndex; if (scriptListExtent == NULL) { return NULL; } for (;;) { scriptIndex++; if (scriptIndex == SCRIPT_LIST_EXTENT_SIZE) { scriptListExtent = scriptListExtent->next; scriptIndex = 0; } else if (scriptIndex >= scriptListExtent->length) { scriptListExtent = NULL; } if (scriptListExtent == NULL) { break; } Script* script = &(scriptListExtent->scripts[scriptIndex]); if ((script->flags & SCRIPT_FLAG_0x02) == 0 && ((script->sp.built_tile & 0xE0000000) >> 29) == gScriptsEnumerationElevation) { break; } } Script* script; if (scriptListExtent != NULL) { script = &(scriptListExtent->scripts[scriptIndex]); } else { script = NULL; } gScriptsEnumerationScriptIndex = scriptIndex; gScriptsEnumerationScriptListExtent = scriptListExtent; return script; } // 0x4A65F0 void _scr_spatials_enable() { _scr_SpatialsEnabled = true; } // 0x4A6600 void _scr_spatials_disable() { _scr_SpatialsEnabled = false; } // 0x4A6610 bool scriptsExecSpatialProc(Object* object, int tile, int elevation) { if (object == gGameMouseBouncingCursor) { return false; } if (object == gGameMouseHexCursor) { return false; } if ((object->flags & OBJECT_HIDDEN) != 0 || (object->flags & OBJECT_FLAT) != 0) { return false; } if (tile < 10) { return false; } if (!_scr_SpatialsEnabled) { return false; } _scr_SpatialsEnabled = false; int builtTile = ((elevation << 29) & 0xE0000000) | tile; for (Script* script = scriptGetFirstSpatialScript(elevation); script != NULL; script = scriptGetNextSpatialScript()) { if (builtTile == script->sp.built_tile) { // NOTE: Uninline. scriptSetObjects(script->sid, object, NULL); } else { if (script->sp.radius == 0) { continue; } int distance = tileDistanceBetween(script->sp.built_tile & 0x3FFFFFF, tile); if (distance > script->sp.radius) { continue; } // NOTE: Uninline. scriptSetObjects(script->sid, object, NULL); } scriptExecProc(script->sid, SCRIPT_PROC_SPATIAL); } _scr_SpatialsEnabled = true; return true; } // scr_load_all_scripts // 0x4A677C int scriptsExecStartProc() { for (int scriptListIndex = 0; scriptListIndex < SCRIPT_TYPE_COUNT; scriptListIndex++) { ScriptList* scriptList = &(gScriptLists[scriptListIndex]); ScriptListExtent* extent = scriptList->head; while (extent != NULL) { for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { Script* script = &(extent->scripts[scriptIndex]); scriptExecProc(script->sid, SCRIPT_PROC_START); } extent = extent->next; } } return 0; } // 0x4A67DC void scriptsExecMapEnterProc() { scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_ENTER); } // 0x4A67E4 void scriptsExecMapUpdateProc() { scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_UPDATE); } // scr_exec_map_update_scripts // 0x4A67EC void scriptsExecMapUpdateScripts(int proc) { _scr_SpatialsEnabled = false; int fixedParam = 0; if (proc == SCRIPT_PROC_MAP_ENTER) { fixedParam = (gMapHeader.flags & 1) == 0; } else { scriptExecProc(gMapSid, proc); } int sidListCapacity = 0; for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(gScriptLists[scriptType]); ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { sidListCapacity += scriptListExtent->length; scriptListExtent = scriptListExtent->next; } } if (sidListCapacity == 0) { return; } int* sidList = (int*)internal_malloc(sizeof(*sidList) * sidListCapacity); if (sidList == NULL) { debugPrint("\nError: scr_exec_map_update_scripts: Out of memory for sidList!"); return; } int sidListLength = 0; for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(gScriptLists[scriptType]); ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { for (int scriptIndex = 0; scriptIndex < scriptListExtent->length; scriptIndex++) { Script* script = &(scriptListExtent->scripts[scriptIndex]); if (script->sid != gMapSid && script->procs[proc] > 0) { sidList[sidListLength++] = script->sid; } } scriptListExtent = scriptListExtent->next; } } if (proc == SCRIPT_PROC_MAP_ENTER) { for (int index = 0; index < sidListLength; index++) { Script* script; if (scriptGetScript(sidList[index], &script) != -1) { script->fixedParam = fixedParam; } scriptExecProc(sidList[index], proc); } } else { for (int index = 0; index < sidListLength; index++) { scriptExecProc(sidList[index], proc); } } internal_free(sidList); _scr_SpatialsEnabled = true; } // 0x4A69A0 void scriptsExecMapExitProc() { scriptsExecMapUpdateScripts(SCRIPT_PROC_MAP_EXIT); } // 0x4A6B64 int scriptsGetMessageList(int a1, MessageList** messageListPtr) { if (a1 == -1) { return -1; } int messageListIndex = a1 - 1; MessageList* messageList = &(_script_dialog_msgs[messageListIndex]); if (messageList->entries_num == 0) { char scriptName[20]; scriptName[0] = '\0'; scriptsGetFileName(messageListIndex & 0xFFFFFF, scriptName); char* pch = strrchr(scriptName, '.'); if (pch != NULL) { *pch = '\0'; } char path[COMPAT_MAX_PATH]; sprintf(path, "dialog\\%s.msg", scriptName); if (!messageListLoad(messageList, path)) { debugPrint("\nError loading script dialog message file!"); return -1; } if (!messageListFilterBadwords(messageList)) { debugPrint("\nError filtering script dialog message file!"); return -1; } } *messageListPtr = messageList; return 0; } // 0x4A6C50 char* _scr_get_msg_str(int messageListId, int messageId) { return _scr_get_msg_str_speech(messageListId, messageId, 0); } // message_str // 0x4A6C5C char* _scr_get_msg_str_speech(int messageListId, int messageId, int a3) { if (messageListId == 0 && messageId == 0) { return _blank_str; } if (messageListId == -1 && messageId == -1) { return _blank_str; } if (messageListId == -2 && messageId == -2) { MessageListItem messageListItem; return getmsg(&gProtoMessageList, &messageListItem, 650); } MessageList* messageList; if (scriptsGetMessageList(messageListId, &messageList) == -1) { debugPrint("\nERROR: message_str: can't find message file: List: %d!", messageListId); return NULL; } if (((gGameDialogHeadFid & 0xF000000) >> 24) != 8) { a3 = 0; } MessageListItem messageListItem; messageListItem.num = messageId; if (!messageListGetItem(messageList, &messageListItem)) { debugPrint("\nError: can't find message: List: %d, Num: %d!", messageListId, messageId); return _err_str; } if (a3) { if (_gdialogActive()) { if (messageListItem.audio != NULL && messageListItem.audio[0] != '\0') { if (messageListItem.flags & 0x01) { gameDialogStartLips(NULL); } else { gameDialogStartLips(messageListItem.audio); } } else { debugPrint("Missing speech name: %d\n", messageListItem.num); } } } return messageListItem.text; } // 0x4A6D64 int scriptGetLocalVar(int sid, int variable, int* value) { if (sid >> 24 == SCRIPT_TYPE_SYSTEM) { debugPrint("\nError! System scripts/Map scripts not allowed local_vars! "); _tempStr1[0] = '\0'; scriptsGetFileName(sid & 0xFFFFFF, _tempStr1); debugPrint(":%s\n", _tempStr1); *value = -1; return -1; } Script* script; if (scriptGetScript(sid, &script) == -1) { *value = -1; return -1; } if (script->localVarsCount == 0) { // NOTE: Uninline. _scr_find_str_run_info(script->field_14, &(script->field_50), sid); } if (script->localVarsCount > 0) { if (script->localVarsOffset == -1) { script->localVarsOffset = _map_malloc_local_var(script->localVarsCount); } *value = mapGetLocalVar(script->localVarsOffset + variable); } return 0; } // 0x4A6E58 int scriptSetLocalVar(int sid, int variable, int value) { Script* script; if (scriptGetScript(sid, &script) == -1) { return -1; } if (script->localVarsCount == 0) { // NOTE: Uninline. _scr_find_str_run_info(script->field_14, &(script->field_50), sid); } if (script->localVarsCount <= 0) { return -1; } if (script->localVarsOffset == -1) { script->localVarsOffset = _map_malloc_local_var(script->localVarsCount); } mapSetLocalVar(script->localVarsOffset + variable, value); return 0; } // Performs combat script and returns true if default action has been overriden // by script. // // 0x4A6EFC bool _scr_end_combat() { if (gMapSid == 0 || gMapSid == -1) { return false; } int team = _combat_player_knocked_out_by(); if (team == -1) { return false; } Script* before; if (scriptGetScript(gMapSid, &before) != -1) { before->fixedParam = team; } scriptExecProc(gMapSid, SCRIPT_PROC_COMBAT); bool success = false; Script* after; if (scriptGetScript(gMapSid, &after) != -1) { if (after->scriptOverrides != 0) { success = true; } } return success; } // 0x4A6F70 int _scr_explode_scenery(Object* a1, int tile, int radius, int elevation) { int scriptExtentsCount = gScriptLists[SCRIPT_TYPE_SPATIAL].length + gScriptLists[SCRIPT_TYPE_ITEM].length; if (scriptExtentsCount == 0) { return 0; } int* scriptIds = (int*)internal_malloc(sizeof(*scriptIds) * scriptExtentsCount * SCRIPT_LIST_EXTENT_SIZE); if (scriptIds == NULL) { return -1; } ScriptListExtent* extent; int scriptsCount = 0; _scr_SpatialsEnabled = false; extent = gScriptLists[SCRIPT_TYPE_ITEM].head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* script = &(extent->scripts[index]); if (script->procs[SCRIPT_PROC_DAMAGE] <= 0 && script->program == NULL) { scriptExecProc(script->sid, SCRIPT_PROC_START); } if (script->procs[SCRIPT_PROC_DAMAGE] > 0) { Object* self = script->owner; if (self != NULL) { if (self->elevation == elevation && tileDistanceBetween(self->tile, tile) <= radius) { scriptIds[scriptsCount] = script->sid; scriptsCount += 1; } } } } extent = extent->next; } extent = gScriptLists[SCRIPT_TYPE_SPATIAL].head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* script = &(extent->scripts[index]); if (script->procs[SCRIPT_PROC_DAMAGE] <= 0 && script->program == NULL) { scriptExecProc(script->sid, SCRIPT_PROC_START); } if (script->procs[SCRIPT_PROC_DAMAGE] > 0 && (script->sp.built_tile & 0xE0000000) >> 29 == elevation && tileDistanceBetween(script->sp.built_tile & 0x3FFFFFF, tile) <= radius) { scriptIds[scriptsCount] = script->sid; scriptsCount += 1; } } extent = extent->next; } for (int index = 0; index < scriptsCount; index++) { Script* script; int sid = scriptIds[index]; if (scriptGetScript(sid, &script) != -1) { script->fixedParam = 20; } // TODO: Obtaining script twice, probably some inlining. if (scriptGetScript(sid, &script) != -1) { script->source = NULL; script->target = a1; } scriptExecProc(sid, SCRIPT_PROC_DAMAGE); } // TODO: Redundant, we already know `scriptIds` is not NULL. if (scriptIds != NULL) { internal_free(scriptIds); } _scr_SpatialsEnabled = true; return 0; }