[Fo1 & Fo2] HOW TO: Implement a proper countdown timer. (Oil Rig, The Vats, Master)

Discussion in 'Fallout General Modding' started by Sduibek, Dec 24, 2012.

  1. Sduibek

    Sduibek Creator of Fallout Fixt Moderator Modder

    Oct 27, 2010


    I don't know if anyone will use this, but maybe someday it'll come in handy for someone 8-)

    It's always bugged me that countdown timers are run via map_update_proc (map_update_p_proc for Fallout 1) because this is called every 10 seconds by the game, roughly - but not an exact interval. It's unreliable and not steady and just a shitty way to display a continuous countdown.

    So, using internal game timers I've made a much better one.

    Originally I had it running under critter_proc but those checks run so often (every game tick) that it's a waste of cycles to put it in there. Instead, this runs roughly once a second as a timed event under timed_event_proc.

    Concept:
    Basically a global variable has been established with "current game time (in seconds) + X seconds" as its value.
    "X" is the timer length for the bomb (or whatever), which will be set at some point during gameplay.
    We take that and subtract the current game time (in seconds), resulting in a SecondsLeft amount (Y).
    We check if the Y is divisible by five (" % 5 == 0"), and if so, print a "Seconds left: Y" message to the screen.
    A flag keeps track of this to make sure the "Seconds left" message is only displayed once for each five-second period. (i.e. no duplicate messages)

    - This could of course be very easily changed to update any other number of seconds, by changing the modulus (%) calculation. So you could do "if (Y % 1 == 0)" to have it display time left every second or "if (Y % 10 == 0)" to display every 10 seconds.


    Example, Fallout 1:
    Code:
    THIS GOES IN OBJ_DUDE.SSL, UNDER "TIMED_EVENT_PROC"
    
    								if (fixed_param == 9) then begin
    										SecondsLeft := (global_var(147) - (game_time / 10));
    										if ((SecondsLeft % 5) == 0) and (SecondsFLAG == 0)  then begin
    											SecondsFLAG := 1;
    											display_msg("Seconds left:" + SecondsLeft + " seconds.");
    										end
    										else begin
    											if (SecondsLeft % 5) then begin
    												SecondsFLAG := 0;
    											end
    										end
    										if ((global_var(147) - (game_time / 10)) == 0) then begin
    											play_gmovie(3);
    											metarule(13, 0);
    										end
    										add_timer_event(dude_obj, 10, 9);
    									end
    								end
    								
    Because of how Fallout handles scripts, the above won't run during combat. So in my case, here's what I'm using to make the timer display once each combat round (which conveniently also happens to be five seconds):

    fixed_param == 4 means "when a combat turn ends" and this code should go in obj_dude script under combat_p_proc

    Code:
    THIS GOES IN OBJ_DUDE.SSL, UNDER "COMBAT_PROC"
    
    	if (fixed_param == 4) then begin
    			if ((global_var(147) - (game_time / 10)) > 0) then begin // if there's still seconds left on the timer
    				variable LVar0 := 0;
    				LVar0 := (global_var(147) - (game_time / 10));
    				if ((SecondsLeft - LVar0) >= 5) then begin // if the current seconds left (during combat) is greater than the SecondsLeft value calculated before combat began
    					display_msg(message_str(443, 100) + LVar0 + message_str(443, 101)); // display current seconds left
    				end
    			end
    			else begin
    				play_gmovie(3);//------ Military Base goes boom and dies, sweet!
    				metarule(13, 0);
    			end
    		end
    	end
    LINE-BY-LINE BREAKDOWN OF THE CODE:

    Code:
    This runs when the timer in question is triggered. For example when blowing up the Vats or blowing up the Oil Rig:
    
    add_timer_event(dude_obj, 0, Z);
    ^ this command runs just once. 0 means it runs immediately. In my current implementation, it's called by the Vats Computer script (VConComp.int) whenever the player triggers the bomb timer. Z is the internal identifier of the countdown timer process, you'll see that below. The X value (timer length in seconds, you'll see that below) is also set in the same script command.


    The following belongs in the obj_dude script file:


    Code:
    if (fixed_param == Z) then begin
    Z is an integer. Value of Z is pretty much arbitrary. For example, in vanilla, obj_dude is already using timers numbered 3 through 8, so it would make most sense to have this be "fixed_param == 9" in that case. This means you'd also use 9 in the VConComp script and later in this script (see below) when calling the timer.

    Code:
    	SecondsLeft := (global_var(X) - (game_time / 10));
    SecondsLeft should be obvious; it's the "Y" value from 'Concept' section above. Basically of the original countdown, how many of those seconds still remain.
    The global_var(X), as in 'Concept' above, should point to whatever variable is holding the countdown amount in seconds. For example if the bomb was set to detonate in 1 minute, global_var(X) should hold the value of 60 plus whatever the (game_time / 10) was when it was triggered.
    "game_time / 10" is crucial as this converts from "ticks" to seconds. If you want to read the current time that's passed since the player was created, use "game_time / 10".

    Code:
    	if ((SecondsLeft % DisplayThisOften) == 0) and (SecondsFLAG == 0)  then begin
    DisplayThisOften is an integer, and the value should be how often (in seconds) you want the "Time Left: Y" message to be displayed to the player. I'm using 5 for this value. The percent sign is a modulus operator, which gives the remainder after dividing by that value. So for my example, modulus 5 == 0 means if the number divided by five has no remainder.
    "(SecondsFLAG == 0)" checks the flag to make sure it's only displaying once per DisplayThisOften (i.e. no duplicate messages to user).

    Code:
    		SecondsFLAG := 1;
    Set the flag when displaying the message.

    Code:
    		display_msg("Seconds left:" + SecondsLeft + " seconds.");
    		end
    Tell user how many seconds remain of the countdown timer.

    Code:
    	else begin
    If it is NOT the case that both (SecondsLeft % 5 == 0) and (SecondsFLAG == 0),

    Code:
    		if (SecondsLeft % DisplayThisOften) then begin
    				SecondsFLAG := 0;
    			end
    		end
    Okay, here notice the check isn't using the "is equal to" operator ("=="). That means this is just a truth check. A truth check runs the math operation and checks if the result is a NON-ZERO VALUE. In this case, if SecondsLeft divided by five has a remainder (the amount of seconds left isn't divisible by five), then un-set the flag. This allows the display (the If statement above) to run the next time (SecondsLeft % 5) == 0.

    Code:
    	if ((global_var(X) - (game_time / 10)) == 0) then begin 
    If the current game time has reached the value of (game_time when countdown was triggered) + (countdown amount). This means the bomb (or whatever) countdown has gotten to 0 seconds. In this case, that means:

    Code:
    			play_gmovie(3);  //   Military Base goes boom and dies, sweet!
    			metarule(13, 0);  //  Used for tracking if the game is over.
    		end
    You could of course have anything happen here. Player dies, Game ends, movie plays, map changes, whatever.

    Code:
    		add_timer_event(dude_obj, Ticks, Z);
    	end
    Finally, call this timer so that it repeats. "Ticks" means a value in game ticks. For example if you put 10 here, that means once a second. In my example, it looks like "add_timer_event(dude_obj, 10, 9);" meaning all the code above will run again in 1 second.