-
Notifications
You must be signed in to change notification settings - Fork 999
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
base: master
Are you sure you want to change the base?
Conversation
As I was playing through the Wanderer campaign, I noticed another place where it seemed like the I changed the
...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. |
There was a problem hiding this 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/Timer.h
Outdated
void Step(PlayerInfo &player, UI *ui); | ||
|
||
private: | ||
enum ResetCondition { NONE, PAUSE, LEAVE_ZONE, LEAVE_SYSTEM }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enum class please
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 enum
s do currently seem to have all-caps names...
source/Timer.cpp
Outdated
if(proximityObject.HasValidPlanet()) | ||
centers.push_back(proximityObject.Position()); | ||
} | ||
for(Point ¢er : centers) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 &
?
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. |
7c63f58
to
02e4738
Compare
…ion name, for passing to the action
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
I've added some comments to |
Better 👍 |
… 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()
Co-authored-by: Rising Leaf <85687254+RisingLeaf@users.noreply.github.com>
…std::set at RisingLeaf's suggestion
tests/integration/config/plugins/integration-tests/data/tests/tests_timers.txt
Outdated
Show resolved
Hide resolved
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 alist
, 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, whileset
cannot?
There was a problem hiding this comment.
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
tests/integration/config/plugins/integration-tests/data/tests/tests_timers.txt
Show resolved
Hide resolved
source/Timer.cpp
Outdated
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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
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! |
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
save some checks
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()))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&& !flagship->IsReversing() && flagship->Velocity().LengthSquared() < idleMaxSpeed); | |
&& !flagship->IsReversing() && flagship->Velocity().LengthSquared() < idleMaxSpeed); |
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; | ||
} | ||
} |
There was a problem hiding this comment.
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
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; | |
} | |
} |
There was a problem hiding this comment.
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
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
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.