Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(enhancement): Add "timer" trigger for missions #9089

Open
wants to merge 60 commits into
base: master
Choose a base branch
from

Conversation

danaris
Copy link
Contributor

@danaris danaris commented Jul 28, 2023

Feature: This PR implements the feature request detailed and discussed in issue #4475

Adds a new timer element to mission specifications, allowing for actions to trigger after a certain amount of time spent in a given system.

Feature Details

The element has the following format:

timer "<Timer Name>" <base time> [random time]
system "<system name>"
[idle]
[uncloaked]
[proximity <radius> [close|far]]
["<planet name>"]
on timeup
<action/conversation>

UI Screenshots

N/A

Usage Examples

This PR includes a modification of the "Sad Archie" mission to use the timer rather than the existing Timer Ship mechanism.

Testing Done

  • the timer correctly loads from and saves to the datafiles
  • it times the number of seconds specified
  • it pauses on activity, on cloak, and when the player is too far away from the target zone
  • it correctly triggers the action
  • it correctly saves the fact that the timer completed to the savefile
  • it correctly allows the mission to complete when the timer is complete
  • it saves the time remaining when leaving the system if allowed
  • it saves the time remaining when landing, quitting, and reloading the save if allowed
  • it resets the time remaining on leaving the target zone, on leaving the system, or on any action that pauses the countdown if specified
  • it safely handles the target system being removed from the map after the mission is loaded
  • it correctly prevents the mission from completing when the timer is not yet complete

Automated Tests Added

I have no experience with creating tests, and am unclear on whether this needs them.

Performance Impact

Performance impact should be minimal. It adds a loop through active missions to check for timers, and through the active timers to step through them, but each call of the Timer::Step() method is quite lightweight, and no appreciable performance impact was observed.

@danaris
Copy link
Contributor Author

danaris commented Aug 1, 2023

As I was playing through the Wanderer campaign, I noticed another place where it seemed like the timer functionality would be very useful: in the "Wanderers: Kor Mereti Hacking" mission.

I changed the on enter Mekislepti trigger to the following timers:

	timer "Kor Mereti Hacking Timer A" 5
		system Mekislepti
		uncloaked
		reset "leave system"
		on timeup
			"reputation: Kor Mereti" = 1
	
	timer "Kor Mereti Hacking Timer B" 10
		system Mekislepti
		uncloaked
		on timeup
			"reputation: Kor Mereti" = -10
			dialog
				`When you enter the Mekislepti system, an entire fleet of drones moves to intercept you. Hardly daring to hope, you press the button that activates the Wanderer program. For a fraction of a second, the drones' engines stop firing and they drift as if out of control. Then, with renewed purpose, they move to attack you.`
				`	Oh well. It was worth a shot.`

...and the result produced this video.

It does make me think we might need another flag for timers: one that prevents them from stopping or resetting for any reason. Without something like that, there would be a danger, in this case, of the player leaving the system within the 5 seconds that the Kor Mereti are friendly; I don't know if that would be useful, but it is at least a concern.

@Hecter94 Hecter94 added the mechanics Things dealing with the mechanics & code of how the game works label Aug 2, 2023
Copy link
Member

@quyykk quyykk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Timers basically have a condition that they must fulfill to run (in system, in zone, idling, etc.). If they stop fulfilling this, the timer should by default stop and reset IMO. I think that's a good default to have, because that's usually what you want.

source/Mission.h Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.h Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
data/sheragi/archaeology missions.txt Show resolved Hide resolved
source/Timer.h Outdated
void Step(PlayerInfo &player, UI *ui);

private:
enum ResetCondition { NONE, PAUSE, LEAVE_ZONE, LEAVE_SYSTEM };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum class please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, my lack of C++ experience shows: is it as simple as adding class after the enum? Or is more required here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah basically. You don't need to make the values all caps either

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...Is there a Style Guide on that last part? Because most, though not all, such enums do currently seem to have all-caps names...

source/PlayerInfo.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
@Amazinite Amazinite modified the milestone: 0.10.5 Sep 10, 2023
source/MainPanel.cpp Show resolved Hide resolved
source/Timer.cpp Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.h Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated
if(proximityObject.HasValidPlanet())
centers.push_back(proximityObject.Position());
}
for(Point &center : centers)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be const&. Also instead of adding all the centers to a list, you can check immediately if that specific center is in proximity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good call.

If I do that, is there still anything here that should be made const &?

@danaris
Copy link
Contributor Author

danaris commented Sep 19, 2023

It occurred to me that it might also be useful, at least sometimes, to have the ability to fire an action when a timer resets (to let the player know that they're no longer on target or whatever), so I've added that.

source/Mission.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
Timothy Collett and others added 17 commits October 7, 2023 14:37
Co-authored-by: Nick <quyykk@protonmail.com>
- Change the time parameter from seconds to frames
- Change the default reset behavior from "none" to "pause"
- Add support for a location filter, rather than only a single system specification
Co-authored-by: Nick <quyykk@protonmail.com>
- Change "systems" token to "system"
- Make sure that checking proximity works properly even with multiple instances of specified planet (eg, ringworlds)
- Make mission argument to Load & constructor const
@danaris
Copy link
Contributor Author

danaris commented Jan 10, 2024

I've added some comments to Timer.cpp. Please let me know if they seem helpful or if they still need work.

@RisingLeaf
Copy link
Contributor

I've added some comments to Timer.cpp. Please let me know if they seem helpful or if they still need work.

Better 👍

Timothy Collett added 3 commits January 11, 2024 14:31
… to step them into a new Mission method, to avoid having mutual includes between PlayerInfo and Timer
…pecified as proximity objects in the Timer at instantiation, so this doesn't have to happen every Step()
source/Timer.h Outdated Show resolved Hide resolved
source/Mission.cpp Outdated Show resolved Hide resolved
danaris and others added 2 commits January 11, 2024 16:42
Co-authored-by: Rising Leaf <85687254+RisingLeaf@users.noreply.github.com>
source/Mission.h Outdated
@@ -160,6 +161,10 @@ class Mission {
// Get a list of NPCs associated with this mission. Every time the player
// takes off from a planet, they should be added to the active ships.
const std::list<NPC> &NPCs() const;
// Get a list of timers associated with this mission.
std::list<Timer> &Timers();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again I dont think we need a list here, a std set would be enough?
also we dont need this getter anymore right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • You're right; the getter is no longer being called anywhere
  • Again, I'm afraid I'm not well-acquainted with the distinctions between various STL collection types—I simply copied the usage for NPCs. Is it that there's something different about how Timers work than NPCs that means they should have a set while NPCs have a list, or would this also apply to the Mission's NPC collection?
  • And am I correct in my understanding that the (primary?) difference between the two is that list can have duplicate members, while set cannot?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And am I correct in my understanding that the (primary?) difference between the two is that list can have duplicate members, while set cannot?
yes

Again, I'm afraid I'm not well-acquainted with the distinctions between various STL collection types—I simply copied the usage for NPCs. Is it that there's something different about how Timers work than NPCs that means they should have a set while NPCs have a list, or would this also apply to the Mission's NPC collection?

dont know

Also remove a line from the tests that seems obsolete
source/Mission.h Outdated Show resolved Hide resolved
source/Timer.h Outdated Show resolved Hide resolved
source/Timer.h Outdated Show resolved Hide resolved
source/Timer.h Outdated Show resolved Hide resolved
source/Timer.h Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated Show resolved Hide resolved
source/Timer.cpp Outdated
Comment on lines 271 to 274
bool reset = cond == resetCondition;
reset |= (cond == Timer::ResetCondition::LEAVE_ZONE && resetCondition == Timer::ResetCondition::PAUSE);
reset |= (cond == Timer::ResetCondition::LEAVE_SYSTEM && (resetCondition == Timer::ResetCondition::PAUSE
|| resetCondition == Timer::ResetCondition::LEAVE_ZONE));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seem difficult to read to me. So we reset if the input condition matches the reset condition for this timer, but the input condition and reset condition can differ and we still reset the timer? I feel like calling ResetOn should just always reset the timer, and we only call the function when we need to, instead of calling the function at every possible chance and having the function figure out if it should actually reset.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I moved this in here to avoid duplication, as it would need to be checked every time a reset might happen.

I've added some comments to clarify the intention here, which I have pasted below:

// If the reset condition being passed in is LEAVE_ZONE (ie, the player is no longer in proximity),
// then we also reset if the timer's reset condition is PAUSE (ie, player no longer meets the criteria).
...
// If the reset condition being passed in is LEAVE_SYSTEM, then we also reset if either of the other two
// conditions is specified for the timer, since we are then necessarily outside the zone, and thus the
// clock is stopped.

Please let me know if you still think these tests should be broken back out to the call sites.

source/Timer.cpp Outdated Show resolved Hide resolved
danaris and others added 3 commits January 21, 2024 14:54
Co-authored-by: Amazinite <jsteck2000@gmail.com>
- Save and reload the elapsed time
- Fix some style issues
- Add some comments to a confusing code section
@Saugia Saugia added waiting on OP The OP needs to provide something, e.g, making requested changes, posting assets, etc. waiting on dev A developer needs to do something, e.g. reviewing, merging, resolving disputes, etc. labels May 12, 2024
@danaris danaris requested a review from Amazinite May 17, 2024 14:42
@danaris
Copy link
Contributor Author

danaris commented May 17, 2024

I believe I've gotten all the comments that were not full sentences. The only conversations still open on this are ones where I'm waiting on a reply—and I believe they're also very minor/matters of opinion.

I'd really love to see if this can make it into 0.10.7 next week!

@bene-dictator bene-dictator removed the waiting on OP The OP needs to provide something, e.g, making requested changes, posting assets, etc. label May 18, 2024
Comment on lines +83 to +90
else if(child.Token(0) == "system" && child.Size() > 1)
system = GameData::Systems().Get(child.Token(1));
else if(child.Token(0) == "system")
systems.Load(child);
else if(child.Token(0) == "proximity" && child.Size() > 1)
proximityCenter = GameData::Planets().Get(child.Token(1));
else if(child.Token(0) == "proximity")
proximityCenters.Load(child);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save some checks

Suggested change
else if(child.Token(0) == "system" && child.Size() > 1)
system = GameData::Systems().Get(child.Token(1));
else if(child.Token(0) == "system")
systems.Load(child);
else if(child.Token(0) == "proximity" && child.Size() > 1)
proximityCenter = GameData::Planets().Get(child.Token(1));
else if(child.Token(0) == "proximity")
proximityCenters.Load(child);
else if(child.Token(0) == "system")
{
if(child.Size() > 1)
system = GameData::Systems().Get(child.Token(1));
else
systems.Load(child);
}
else if(child.Token(0) == "proximity")
{
if(child.Size() > 1)
proximityCenter = GameData::Planets().Get(child.Token(1));
else
proximityCenters.Load(child);
}

if(system && (proximityCenter || !proximityCenters.IsEmpty()))
for(const StellarObject &proximityObject : system->Objects())
if(proximityObject.HasValidPlanet() && (proximityCenter == proximityObject.GetPlanet() ||
proximityCenters.Matches(proximityObject.GetPlanet())))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
proximityCenters.Matches(proximityObject.GetPlanet())))
proximityCenters.Matches(proximityObject.GetPlanet())))

looks clearer

bool shipIdle = true;
if(requireIdle)
shipIdle = (!flagship->IsThrusting() && !flagship->IsSteering()
&& !flagship->IsReversing() && flagship->Velocity().LengthSquared() < idleMaxSpeed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
&& !flagship->IsReversing() && flagship->Velocity().LengthSquared() < idleMaxSpeed);
&& !flagship->IsReversing() && flagship->Velocity().LengthSquared() < idleMaxSpeed);

Comment on lines +360 to +369
if(proximityCache.size() > 0)
for(const StellarObject *proximityObject : proximityCache)
{
double dist = flagship->Position().Distance(proximityObject->Position());
if((closeTo && dist <= proximity) || (!closeTo && dist >= proximity))
{
inProximity = true;
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if proximityCache is empty, the loop doesn't do anything anyway; I'd also use a ternary for the proximity conditions

Suggested change
if(proximityCache.size() > 0)
for(const StellarObject *proximityObject : proximityCache)
{
double dist = flagship->Position().Distance(proximityObject->Position());
if((closeTo && dist <= proximity) || (!closeTo && dist >= proximity))
{
inProximity = true;
break;
}
}
for(const StellarObject *proximityObject : proximityCache)
{
double dist = flagship->Position().Distance(proximityObject->Position());
if(closeTo ? dist <= proximity : dist >= proximity)
{
inProximity = true;
break;
}
}

Copy link
Contributor

@RisingLeaf RisingLeaf Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you really want to simplify the conditional just do closeto != (dist >= proximity)
Even though that would ignore the single case of == but that's not important for double values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A suggestion for new content or functionality that requires code changes mechanics Things dealing with the mechanics & code of how the game works waiting on dev A developer needs to do something, e.g. reviewing, merging, resolving disputes, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants