diff --git a/data/avgi/avgi side missions.txt b/data/avgi/avgi side missions.txt index cb4b40ee4af8..6158a3c13fb2 100644 --- a/data/avgi/avgi side missions.txt +++ b/data/avgi/avgi side missions.txt @@ -501,10 +501,12 @@ mission "Avgi Culture: Zapchannel Energy" mission "Damselflyman Abilities" landing + invisible + non-blocking repeat to offer has "damselflyman" - not "outfit: Damselflyman" + not "outfit (flagship installed): Damselflyman" on offer outfit "Damselflyman" 1 fail diff --git a/data/coalition/coalition missions.txt b/data/coalition/coalition missions.txt index 884a6995b2f3..f901c0e61da3 100644 --- a/data/coalition/coalition missions.txt +++ b/data/coalition/coalition missions.txt @@ -1363,10 +1363,34 @@ mission "Arachi Cartoonist 1" ` "In that case, I'll head to their home shipyard and see what I can bring back for you."` goto end ` "Alright, it's a bit of a long trip to , but if you're sure, let's head to my ship and I'll show you your bunk."` + ` "I happen to own a ship matching that description, a Betelgeuse Leviathan. Would you like to see it?"` + goto hasleviathan + to display + has "ship model: Leviathan" ` "Oh, leave I cannot. Whether far away or not this you speak of is, allowed to leave Coalition space, we are not," he says. "Go on without me you must, Captain."` label end ` He thanks you and wishes you safe travels on your journey to .` accept + label hasleviathan + action + "coalition jobs" += 2 + payment 75940 + ` Though reading emotions from a giant spider proves challenging, Drugub appears to be star-struck.` + ` "Own a ship, a 'Leviathan,' you do? To your hangar, we must go!"` + ` After a brief walk to the hangar where your ship is parked, Drugub immediately begins crawling over the Leviathan. It seems what you've provided him with is far more substantial than any secondhand reference could possibly convey. He spends a while examining the ship up close before hopping down and taking many pictures from every conceivable angle -- as well as drawing a few quick sketches.` + ` Once he's finished, he hands you a payment of and spends some more time with you, asking you all the questions he can think of about the ship.` + ` "Very beautiful, this 'Leviathan' is. Thinking of many ways to draw it, I already am," he says. "Maybe a flagship of one of my characters, make it I will..."` + ` He spends some more minutes listening to you after he asks that you describe how your ship performs, thanks you again, and prepares to leave.` + ` "Oh, before I go, Captain , use the name of your own ship for that 'Leviathan,' may I? Just as a small thank you."` + choice + ` "Of course! It's called ."` + ` "The name is nothing special. I wouldn't even get the reference of it if I read your comic in the future, honestly."` + goto noship + ` " it is! Do it justice I will, Captain. Thanks again."` + decline + label noship + ` "Ah, I see. Well, just a reference it would be, so the name matters not, I suppose. Anyway, thanks again, Captain."` + decline @@ -1389,6 +1413,8 @@ mission "Arachi Cartoonist 2" "coalition jobs" += 2 payment 75940 conversation + branch leviathan + has "ship model: Leviathan" `Drugub is excitedly waiting for you when you return. However simple a piece of paper it may seem to you, he grabs the flyer as if it's some scroll of knowledge.` ` He hands you your payment of and spends some time with you still, asking you all the questions he can think of about each ship.` ` "Very beautiful, this 'Leviathan' is. Thinking of many ways to draw it, I already am," he says. "Maybe a flagship of one of my characters, make it I will..."` @@ -1402,7 +1428,21 @@ mission "Arachi Cartoonist 2" accept label noship ` "Ah, I see. Well, just a reference it would be, so the name matters not, I suppose. Anyway, thanks again Captain."` - + accept + label leviathan + `Drugub is excitedly waiting for you when you return. Though reading emotions from a giant spider proves challenging, Drugub appears to be star-struck when you mention you brought back a ship matching what he described.` + ` "Own a ship, a 'Leviathan,' you do? To your hangar, we must go!"` + ` After a brief walk to the hangar where your ship is parked, Drugub immediately begins crawling over the Leviathan. It seems what you've provided him with is far more substantial than any secondhand reference could possibly convey. He spends a while examining the ship up close before hopping down and taking many pictures from every conceivable angle -- as well as drawing a few quick sketches.` + ` Once he's finished, he hands you your payment of and spends some more time with you, asking you all the questions he can think of about the ship.` + ` "Very beautiful, this 'Leviathan' is. Thinking of many ways to draw it, I already am," he says. "Maybe a flagship of one of my characters, make it I will..."` + ` He spends some more minutes listening to you after he asks that you describe how your ship performs, thanks you again, and prepares to leave.` + ` "Oh, before I go, Captain , use the name of your own ship for that 'Leviathan,' may I? Just as a small thank you."` + choice + ` "Of course! It's called ."` + ` "The name is nothing special. I wouldn't even get the reference of it if I read your comic in the future, honestly."` + goto noship + ` " it is! Do it justice I will, Captain. Thanks again."` + accept mission "Coalition: Alpha Encounter" diff --git a/data/coalition/heliarch intro.txt b/data/coalition/heliarch intro.txt index 5155c5a76c22..4d156b2811c1 100644 --- a/data/coalition/heliarch intro.txt +++ b/data/coalition/heliarch intro.txt @@ -17,11 +17,10 @@ mission "Heliarch Investigation 1" description "Bring Heliarch agents to ." passengers 3 source - near "Quaru" 4 10 + near "Quaru" 2 10 destination "Ring of Friendship" to offer random < 65 - "coalition jobs" >= 70 has "license: Coalition" not "joined the lunarium" not "assisting lunarium" @@ -557,7 +556,6 @@ mission "Heliarch Recon 1" destination "Ring of Wisdom" to offer "reputation: Quarg" >= 0 - "coalition jobs" >= 55 has "outfit: Jump Drive" has "visited planet: Lagrange" has "visited planet: Alta Hai" @@ -809,7 +807,6 @@ mission "Heliarch Recon 3-A" has "event: rim archaeology results" not "joined the lunarium" not "assisting lunarium" - "coalition jobs" >= 65 on offer conversation `Upon landing, you're contacted by the Heliarch agents who have had you scan Quarg worlds and ships. "Hello again, Captain !" the Kimek greets you. "Thank you again, we must, for your latest service. Much more detailed than what we had, your scans are."` @@ -1286,15 +1283,14 @@ mission "Heliarch Containment 1" cargo "military supplies" 25 deadline source - near "3 Spring Rising" 2 5 + near "3 Spring Rising" 1 5 destination "Fourth Shadow" to offer has "license: Coalition" has "main plot completed" not "joined the lunarium" not "assisting lunarium" - "coalition jobs" >= 60 - random > 25 + random < 75 on offer conversation `The spaceport on is in quite a lot of turmoil, as dozens of Heliarch agents march around at an accelerated pace. Some prepare to board their own ships, and others speak with some of the captains here. It's no different with you, as a Kimek agent approaches you after noticing you watching.` @@ -1344,7 +1340,7 @@ mission "Heliarch Containment 2" name "Pick Up Injured Soldiers" description "Land on , to pick up the Heliarch soldiers in need of medical attention, and transport them to by ." source - near "Bloptab" 1 3 + near "Bloptab" 1 4 stopover "Delve of Bloptab" destination "Ahr" passengers 21 @@ -1353,7 +1349,7 @@ mission "Heliarch Containment 2" has "Heliarch Containment 1: done" not "joined the lunarium" not "assisting lunarium" - random > 35 + random < 65 on offer conversation `You're stopped by a group of Heliarch agents as you enter the spaceport, most of them Arachi.` @@ -1620,7 +1616,7 @@ mission "Heliarch Drills 1" has "license: Coalition" not "joined the lunarium" not "assisting lunarium" - random < 11 + random < 30 "combat rating" >= 8104 on offer conversation @@ -2062,10 +2058,10 @@ mission "Heliarch License 1" or "assisted heliarch" >= 5 and - "coalition jobs" >= 90 + "coalition jobs" >= 70 "assisted heliarch" == 4 and - "coalition jobs" >= 120 + "coalition jobs" >= 100 "assisted heliarch" == 3 not "joined the lunarium" not "assisting heliarchy" diff --git a/data/coalition/lunarium intro.txt b/data/coalition/lunarium intro.txt index 486ff6b00f6b..982a4f12844b 100644 --- a/data/coalition/lunarium intro.txt +++ b/data/coalition/lunarium intro.txt @@ -26,11 +26,11 @@ mission "Lunarium: Smuggling: Charity 1" minor cargo "charity supplies" 22 source - near "3 Spring Rising" 2 6 + near "3 Spring Rising" 1 6 not government "Heliarch" destination "Fourth Shadow" to offer - random < 40 + random < 55 has "Coalition: First Contact: done" not "joined the heliarchs" not "Lunarium: Questions: active" @@ -96,10 +96,10 @@ mission "Lunarium: Smuggling: Charity 2" passengers 5 cargo "charity supplies" 39 source - near "5 Winter Above" 1 3 + near "5 Winter Above" 1 4 destination "Into White" to offer - random < 50 + random < 60 has "Lunarium: Smuggling: Charity 1: done" not "joined the heliarchs" not "Lunarium: Questions: active" @@ -156,10 +156,10 @@ mission "Lunarium: Smuggling: Charity 3" description "Bring to " cargo "charity supplies" 46 source - near "14 Pole" 3 5 + near "14 Pole" 3 7 destination "Remote Blue" to offer - random < 70 + random < 80 has "Lunarium: Smuggling: Charity 2: done" not "joined the heliarchs" not "Lunarium: Questions: active" @@ -284,7 +284,7 @@ mission "Lunarium: Smuggling: AM" destination "Fourth Shadow" to offer has "Lunarium: Smuggling: Grenades: done" - random < 15 + 3 * "assisted lunarium" * "assisted lunarium" + random < 25 + 4 * "assisted lunarium" * "assisted lunarium" not "joined the heliarchs" not "assisting heliarchy" on offer @@ -329,7 +329,7 @@ mission "Lunarium: Smuggling: Torpedoes" to offer has "Lunarium: Smuggling: AM: done" has "event: deep sky tech available" - random < 20 + 3 * "assisted lunarium" * "assisted lunarium" + random < 30 + 4 * "assisted lunarium" * "assisted lunarium" not "joined the heliarchs" not "assisting heliarchy" on offer @@ -380,7 +380,7 @@ mission "Lunarium: Smuggling: Heat" to offer has "Lunarium: Smuggling: Torpedoes: done" has "event: flamethrower available" - random < 25 + 3 * "assisted lunarium" * "assisted lunarium" + random < 30 + 4 * "assisted lunarium" * "assisted lunarium" not "joined the heliarchs" not "assisting heliarchy" on offer @@ -436,7 +436,7 @@ mission "Lunarium: Smuggling: Reactors" destination "Mebla's Portion" to offer has "Lunarium: Smuggling: Heat: done" - random < 40 + 3 * "assisted lunarium" * "assisted lunarium" + random < 40 + 4 * "assisted lunarium" * "assisted lunarium" not "joined the heliarchs" not "assisting heliarchy" on offer @@ -487,7 +487,7 @@ mission "House Bebliss 1-A" description "Head to , where members of House Bebliss wish to discuss a job offer." minor to offer - random < 10 + random < 15 has "license: Coalition" not "joined the heliarchs" not "assisting heliarchy" @@ -539,7 +539,7 @@ mission "House Bebliss 1-B" name "Meet with House Bebliss" description "Head to , where representatives from an Arach House that supports the Lunarium wish to discuss a job offer." to offer - random < 40 + random < 75 has "House Bebliss 1-A: declined" not "joined the heliarchs" not "assisting heliarchy" @@ -938,10 +938,11 @@ mission "Lunarium: Evacuation 1" passengers 35 "apparent payment" 386730 source - near "3 Spring Rising" 1 4 + not government "Heliarch" + near "3 Spring Rising" 1 5 destination "Fourth Shadow" to offer - random < 40 + random < 55 has "Coalition: First Contact: done" not "joined the heliarchs" not "assisting heliarchy" @@ -1060,10 +1061,10 @@ mission "Lunarium: Evacuation 3" "apparent payment" 748300 source not government "Heliarch" - near "Silver Bell" 2 3 + near "Silver Bell" 1 4 destination "Chosen Nexus" to offer - random < 50 + random < 60 has "Lunarium: Evacuation 2: done" not "joined the heliarchs" not "assisting heliarchy" @@ -1185,10 +1186,10 @@ mission "Lunarium: Evacuation 5" passengers 116 landing source - near "Ablub" + near "Ablub" 1 3 destination "Cool Forest" to offer - random < 80 + random < 85 has "Lunarium: Evacuation 4: done" has "outfit: Jump Drive" not "joined the heliarchs" @@ -1329,10 +1330,10 @@ mission "Lunarium: Propaganda 1" passengers 1 cargo "promotional posters" 1 source - near "Ancient Hope" 2 4 + near "Ancient Hope" 2 5 destination "Bright Echo" to offer - random < 25 + random < 50 has "Coalition: First Contact: done" not "joined the heliarchs" not "assisting heliarchy" @@ -1602,7 +1603,7 @@ mission "Lunarium: Combat Training 1" attributes arach destination "Zug" to offer - random < 20 + random < 65 has "outfit: Jump Drive" or has "Lunarium: Propaganda 4: done" @@ -1847,10 +1848,10 @@ mission "Lunarium: Questions" or "assisted lunarium" >= 5 and - "coalition jobs" >= 80 + "coalition jobs" >= 70 "assisted lunarium" == 4 and - "coalition jobs" >= 110 + "coalition jobs" >= 100 "assisted lunarium" == 3 "reputation: Quarg" >= 0 not "joined the heliarchs" diff --git a/data/human/deep missions.txt b/data/human/deep missions.txt index 7d9c449d8aca..2322c3bfc63a 100644 --- a/data/human/deep missions.txt +++ b/data/human/deep missions.txt @@ -2825,6 +2825,7 @@ mission "Stone of our Fathers 5" dialog `You have reached , but your escort with the space reserved for Thorleif and Ragnhild has not arrived! Better depart and wait for your escorts to arrive in this star system.` on complete event "stone waiting most" 30 + set "stone waiting most: triggered" payment 500000 conversation `The trip back to Norn was restful.` diff --git a/data/human/free worlds 3 checkmate.txt b/data/human/free worlds 3 checkmate.txt index 80c83e4c8ab6..51563e9259e9 100644 --- a/data/human/free worlds 3 checkmate.txt +++ b/data/human/free worlds 3 checkmate.txt @@ -1123,13 +1123,13 @@ mission "FWC Pug 1" npc evade personality entering disables heroic government Pug - fleet 2 + fleet names "pug" variant "Pug Maboro" "Pug Enfolta" "Pug Zibruka" 2 - fleet 3 + fleet 2 names "pug" variant "Pug Enfolta" diff --git a/data/human/human missions.txt b/data/human/human missions.txt index 86807546ce9b..3c46e9af5398 100644 --- a/data/human/human missions.txt +++ b/data/human/human missions.txt @@ -5979,6 +5979,11 @@ mission "Argosy Hijacking" to offer "combat rating" > 200 random < 5 + or + and + not "FW Pug 1: offered" + not "FWC Pug 1: offered" + has "main plot completed" on offer conversation `You're signing the standard Syndicate landing papers with an officer on . "Everything seems to be in order. And of course, we'll have your ship refueled promptly. Have a good day, Captain ." He nods his head at you and starts to move off towards the next ship.` @@ -7516,8 +7521,8 @@ mission "Timothy Radrickson 1: Stowaway" near "Zeta Aquilae" 3 10 government "Republic" "Free Worlds" "Independent" "Pirate" "Neutral" destination "Rand" - passengers 1 to offer + has "event: remembrance day" random < 2 on enter @@ -8005,7 +8010,7 @@ mission "Timothy Radrickson 3d: Return to Rand" has "Timothy Radrickson 3c: Escort the Convoy: done" on complete conversation - `Instead of Regina you are met by Timothy's executive secretary, Wynnafrith. She is clearly a little uncomfortable in Rand's regular gravity, but is doing her best to push through it.` + `Instead of Regina you are met by Timothy's executive secretary, Wynnafrith. She is clearly a little uncomfortable in Rand's low gravity, but is doing her best to push through it.` ` "Captain ! You're the talk of the world! The savior of Rand." She gives you an open and earnest smile.` ` "I've been sent by the Governor to drive you straight to his estate. Please come with me."` choice @@ -8244,7 +8249,7 @@ mission "Timothy Radrickson 5a: Timshank Redemption" ` As your ship approaches the mine you can't help but notice the burnt out remains of a gutted and abandoned town. A few minutes later your ship rattles down outside the mine's entrance.` choice ` (Leave your ship.)` - ` It's dark and dusty outside. The planet's gravity makes you feel slow, and the air uncomfortably heavy in your lungs. The entrance to the mine looms large before you and seems suddenly quite menacing. Perhaps this wasn't the best idea...` + ` It's dark and dusty outside. The planet's low gravity makes you feel languid, and the air uncomfortably light in your lungs. The entrance to the mine looms large before you and seems suddenly quite menacing. Perhaps this wasn't the best idea...` ` "Don't move, or we will end you!" comes a shout from within the darkness of the mine. "Identify yourself, now!"` choice ` "I'm Captain , veteran of the human civil war!"` @@ -8302,9 +8307,9 @@ mission "Timothy Radrickson 5a: Timshank Redemption" choice ` (Walk inside.)` - ` The woman before you is middle-aged and somewhat squat with the hard-won muscles of a person adapted to Rand's grueling gravity. She's attractive though, and there is something very familiar about her face. You've met her before...` - ` "Captain !" She says with a genuine grin. "I doubt you recognize me, but I was Governor Radrickson's secretary, Wynnafrith. We met years back at your banquet."` - ` She glances down at herself as if recognizing her body's transformation from offworld waif to native Rander. "I dare say the world has changed me now that I'm away from the gravity generators.` + ` The woman before you is middle-aged and unusually gaunt, with the hollowed out appearance of a person who's lived too long in Rand's frail gravity. She's attractive though, and there is something very familiar about her face. You've met her before...` + ` "Captain !" she says with a genuine grin. "I doubt you recognize me, but I was Governor Radrickson's secretary, Wynnafrith. We met years back at your banquet."` + ` She glances down at herself as if recognizing her body's transformation from healthy offworlder to Rander waif. "I dare say the world has changed me now that I'm away from the gravity generators.` ` "Although I am no longer a secretary, I am Union-Colonel Wynnfrith, one of the leaders of the Underground Workers Union!" Her eyes sparkle and her words ring with a deadly seriousness.` ` She tilts her head slightly. "But that's enough about me. What possible reason could you have for coming here?" Her face is open and friendly, but she casually rests her hand on her side arm.` choice @@ -10205,3 +10210,14 @@ mission "Stone to Hope: Thanks" ` He returns to tinkering with the repulsor but continues, "There's no money or reward, but a good deed is its own reward, isn't it?` ` "At least that's what I believe," he says more to himself than to you.` decline + +# For compatibility with saves that completed Stones of our Fathers 5, but didn't get the new event connected to it. +mission "Patch Stone Waiting" + landing + invisible + to offer + has "Stone of our Fathers 5: done" + not "stone waiting most: triggered" + on offer + event "stone waiting most" 14 + fail diff --git a/data/incipias/incipias first contact.txt b/data/incipias/incipias first contact.txt index 1375730d051a..6cc9999e78ac 100644 --- a/data/incipias/incipias first contact.txt +++ b/data/incipias/incipias first contact.txt @@ -323,6 +323,7 @@ mission "Incipias: Help The Stranded 2" passengers 5 source Turra destination "Pon'tes" + blocked "You have landed on , but you don't have enough passenger space for the Incipias. Return here when you have the required space free." to offer has "Incipias: Help The Stranded 1: done" on offer diff --git a/data/kahet/kahet missions.txt b/data/kahet/kahet missions.txt index 27bd5b4368ba..60ffa94467f7 100644 --- a/data/kahet/kahet missions.txt +++ b/data/kahet/kahet missions.txt @@ -31,7 +31,6 @@ mission "Disabled Ka'het" government "Ka'het" "Ka'het (Infighting)" destination "Earth" on offer - log "Successfully boarded a Ka'het. It appears to be a single, ship-sized organism that can travel in space using a special exoskeleton." conversation branch "boarded aberrant" diff --git a/data/map systems.txt b/data/map systems.txt index cb6509af5ffa..7e05e4a4e298 100644 --- a/data/map systems.txt +++ b/data/map systems.txt @@ -23013,11 +23013,11 @@ system Kella-Uoasa period 5000 object Kasii-Tuur-Saqru sprite "planet/station successor 2" - distance 180 + distance 464 period 64 object sprite planet/ice8 - distance 700 + distance 1000 period 1000 object Giiq-Sou-Kella sprite planet/ocean9 diff --git a/data/remnant/remnant 2 cognizance.txt b/data/remnant/remnant 2 cognizance.txt index b6bdf1539a9b..92f78d6142c6 100644 --- a/data/remnant/remnant 2 cognizance.txt +++ b/data/remnant/remnant 2 cognizance.txt @@ -662,8 +662,8 @@ mission "Remnant: Cognizance 18" event "remnant: pantica void sprites" event "remnant: cognizance timer" 30 conversation - `Moments later a ping comes in on the commlink, and Prefect Chilia and Plume appear on screen. "Thank you for your work, Captain. It is unclear what the results of this effort will be, but for now, we wait and see. Hopefully the void sprites settle in," concludes Plume.` - ` "In the meantime," continues Chilia. "We are allocating you a daily stipend from our resources to cover some of your ongoing crew, maintenance, and upgrading costs. A larger one-time allotment has already been credited to your account with the shipyard facilities to help cover costs incurred during this mission."` + `Moments later a ping comes in on the commlink, and Prefect Chilia appears on screen accompanied by Plume. Plume gestures first. "Thank you for your work, Captain. It is unclear what the results of this effort will be, but for now, we wait and see. Hopefully the void sprites settle in," he concludes.` + ` "In the meantime," continues Chilia, "We are allocating you a daily stipend from our resources to cover some of your ongoing crew, maintenance, and upgrading costs. A larger one-time allotment has already been credited to your account with the shipyard facilities to help cover costs incurred during this mission."` @@ -745,7 +745,7 @@ mission "Remnant: Cognizance 20" ` "Because we are in the midst of war, and located in an actively hazardous part of the galaxy. We, as a people, need to make countless thousands of on-the-spot decisions every day. If we had to reach a consensus for every troop movement, expedition, or resource allocation we would be overwhelmed within days." She stops speaking, and another Remnant picks up the explanation.` ` "By the same token, prefects can be killed, or overloaded. Having as much decision making as possible handled by the Remnant as a group reduces our vulnerability to assassination strikes, and helps protect our prefects from burning out under the responsibility. On the flip side, it also protects our society from becoming an autocracy where one powerful person or small group makes all the decisions."` label missionchoice - ` The Remnant still and look at you. "So, will you take this mission?"` + ` The Remnant become still and look at you. "So, will you take this mission?"` choice ` "Yes."` ` "Later."` @@ -781,7 +781,8 @@ mission "Remnant: Cognizance 21" government "Remnant" attributes "remnant primary" stopover "Ssil Vida" - blocked "You need 10 free space and a Research Laboratory for this mission." + blocked "You need 10 free space, five free bunks, and a Research Laboratory for this mission." + passengers 5 cargo "supplies" 10 to offer has "Remnant: Cognizance 20: done" diff --git a/images/ui/outfitter selected+.png b/images/ui/outfitter selected+.png index 0777a33d8908..5a8cf9bd0463 100644 Binary files a/images/ui/outfitter selected+.png and b/images/ui/outfitter selected+.png differ diff --git a/source/AI.cpp b/source/AI.cpp index 7230b2398820..a52b172e8ff6 100644 --- a/source/AI.cpp +++ b/source/AI.cpp @@ -2849,10 +2849,12 @@ void AI::MoveToAttack(Ship &ship, Command &command, const Body &target) // use them to reach the target more quickly. if(facing < -.75 && ship.Attributes().Get("reverse thrust")) command.SetThrust(-1.); - // This isn't perfect, but it works well enough. + // Only apply thrust if either: + // This ship is within 90 degrees of facing towards its target and far enough away not to overshoot + // if it accelerates while needing to turn further, or: + // This ship is moving away from its target but facing mostly towards it. else if((facing >= 0. && direction.Length() > diameter) - || (ship.Velocity().Dot(direction) < 0. && - facing) >= .9) + || (ship.Velocity().Dot(direction) < 0. && facing >= .9)) command.SetThrust(1.); // Use an equipped afterburner if possible. diff --git a/source/ConditionAssignments.cpp b/source/ConditionAssignments.cpp index 763f69995593..b01fc5ddbfeb 100644 --- a/source/ConditionAssignments.cpp +++ b/source/ConditionAssignments.cpp @@ -28,13 +28,13 @@ using namespace std; namespace { const auto ASSIGN_OP_TO_TEXT = map { - {ConditionAssignments::AssignOp::AO_ASSIGN, "="}, - {ConditionAssignments::AssignOp::AO_ADD, "+="}, - {ConditionAssignments::AssignOp::AO_SUB, "-="}, - {ConditionAssignments::AssignOp::AO_MUL, "*="}, - {ConditionAssignments::AssignOp::AO_DIV, "/="}, - {ConditionAssignments::AssignOp::AO_LT, "?="}, + {ConditionAssignments::AssignOp::ASSIGN, "="}, + {ConditionAssignments::AssignOp::ADD, "+="}, + {ConditionAssignments::AssignOp::SUB, "-="}, + {ConditionAssignments::AssignOp::MUL, "*="}, + {ConditionAssignments::AssignOp::DIV, "/="}, + {ConditionAssignments::AssignOp::LT, "?="}, }; } @@ -100,25 +100,25 @@ void ConditionAssignments::Apply(ConditionsStore &conditions) const int64_t newValue = assignment.expressionToEvaluate.Evaluate(conditions); switch(assignment.assignOperator) { - case AssignOp::AO_ASSIGN: + case AssignOp::ASSIGN: ce = newValue; break; - case AssignOp::AO_ADD: + case AssignOp::ADD: ce += newValue; break; - case AssignOp::AO_SUB: + case AssignOp::SUB: ce -= newValue; break; - case AssignOp::AO_MUL: + case AssignOp::MUL: ce = static_cast(ce) * newValue; break; - case AssignOp::AO_DIV: + case AssignOp::DIV: ce = newValue ? static_cast(ce) / newValue : numeric_limits::max(); break; - case AssignOp::AO_LT: + case AssignOp::LT: ce = min(static_cast(ce), newValue); break; - case AssignOp::AO_GT: + case AssignOp::GT: ce = max(static_cast(ce), newValue); break; } @@ -143,7 +143,7 @@ set ConditionAssignments::RelevantConditions() const void ConditionAssignments::AddSetCondition(const std::string &name) { - assignments.emplace_back(name, AssignOp::AO_ASSIGN, ConditionSet(1)); + assignments.emplace_back(name, AssignOp::ASSIGN, ConditionSet(1)); } @@ -157,7 +157,7 @@ void ConditionAssignments::Add(const DataNode &node) node.PrintTrace("Parse error; " + node.Token(0) + " keyword requires a single valid condition:"); return; } - assignments.emplace_back(node.Token(1), AssignOp::AO_ASSIGN, ConditionSet(node.Token(0) == "set" ? 1 : 0)); + assignments.emplace_back(node.Token(1), AssignOp::ASSIGN, ConditionSet(node.Token(0) == "set" ? 1 : 0)); } else if(node.Size() == 2 && (node.Token(1) == "++" || node.Token(1) == "--")) { @@ -166,13 +166,13 @@ void ConditionAssignments::Add(const DataNode &node) node.PrintTrace("Parse error; " + node.Token(1) + " operator requires a single valid condition:"); return; } - assignments.emplace_back(node.Token(0), node.Token(1) == "++" ? AssignOp::AO_ADD : AssignOp::AO_SUB, + assignments.emplace_back(node.Token(0), node.Token(1) == "++" ? AssignOp::ADD : AssignOp::SUB, ConditionSet(1)); } else if(node.Size() >= 3) { // Parse the assignment operator. - AssignOp ao = AssignOp::AO_ASSIGN; + AssignOp ao = AssignOp::ASSIGN; const string assignOpString = node.Token(1); auto it = find_if(ASSIGN_OP_TO_TEXT.begin(), ASSIGN_OP_TO_TEXT.end(), [&assignOpString](const std::pair &e) { diff --git a/source/ConditionAssignments.h b/source/ConditionAssignments.h index a1e331f9dcc3..4fddd6316f80 100644 --- a/source/ConditionAssignments.h +++ b/source/ConditionAssignments.h @@ -30,6 +30,19 @@ class DataWriter; // Condition assignments is a collection of assignment operations that can be applied on the player's set of named // "conditions" to modify them. class ConditionAssignments { +public: + /// Possible assignment operators. + enum class AssignOp { + ASSIGN, /// Used for =, set (with 1 as expression), clear ((with 0 as expression) + ADD, /// Used for +=, ++ (with 1 as expression) + SUB, /// Used for -=, -- (with 1 as expression) + MUL, /// Used for *= + DIV, /// Used for /= (integer division) + LT, /// Used for ?= + }; + + public: ConditionAssignments() = default; @@ -59,20 +72,6 @@ class ConditionAssignments { void Add(const DataNode &node); -public: - /// Possible assignment operators. - enum class AssignOp - { - AO_ASSIGN, /// Used for =, set (with 1 as expression), clear ((with 0 as expression) - AO_ADD, /// Used for +=, ++ (with 1 as expression) - AO_SUB, /// Used for -=, -- (with 1 as expression) - AO_MUL, /// Used for *= - AO_DIV, /// Used for /= (integer division) - AO_LT, /// Used for ?= - }; - - private: // Class that supports a single assignment class Assignment { diff --git a/source/ConditionSet.cpp b/source/ConditionSet.cpp index 054a264d0b7b..45110bbcbdf5 100644 --- a/source/ConditionSet.cpp +++ b/source/ConditionSet.cpp @@ -41,17 +41,17 @@ namespace // "Apply" operators return the value that the condition should have // after applying the expression. static const map opMap = { - {ConditionSet::ExpressionOp::OP_EQ, [](int64_t a, int64_t b) -> int64_t { return a == b; }}, - {ConditionSet::ExpressionOp::OP_NE, [](int64_t a, int64_t b) -> int64_t { return a != b; }}, - {ConditionSet::ExpressionOp::OP_LT, [](int64_t a, int64_t b) -> int64_t { return a < b; }}, - {ConditionSet::ExpressionOp::OP_GT, [](int64_t a, int64_t b) -> int64_t { return a > b; }}, - {ConditionSet::ExpressionOp::OP_LE, [](int64_t a, int64_t b) -> int64_t { return a <= b; }}, - {ConditionSet::ExpressionOp::OP_GE, [](int64_t a, int64_t b) -> int64_t { return a >= b; }}, - {ConditionSet::ExpressionOp::OP_MOD, [](int64_t a, int64_t b) { return b ? a % b : a; }}, - {ConditionSet::ExpressionOp::OP_MUL, [](int64_t a, int64_t b) { return a * b; }}, - {ConditionSet::ExpressionOp::OP_ADD, [](int64_t a, int64_t b) { return a + b; }}, - {ConditionSet::ExpressionOp::OP_SUB, [](int64_t a, int64_t b) { return a - b; }}, - {ConditionSet::ExpressionOp::OP_DIV, [](int64_t a, int64_t b) { return b ? a / b : numeric_limits::max(); }} + {ConditionSet::ExpressionOp::EQ, [](int64_t a, int64_t b) -> int64_t { return a == b; }}, + {ConditionSet::ExpressionOp::NE, [](int64_t a, int64_t b) -> int64_t { return a != b; }}, + {ConditionSet::ExpressionOp::LT, [](int64_t a, int64_t b) -> int64_t { return a < b; }}, + {ConditionSet::ExpressionOp::GT, [](int64_t a, int64_t b) -> int64_t { return a > b; }}, + {ConditionSet::ExpressionOp::LE, [](int64_t a, int64_t b) -> int64_t { return a <= b; }}, + {ConditionSet::ExpressionOp::GE, [](int64_t a, int64_t b) -> int64_t { return a >= b; }}, + {ConditionSet::ExpressionOp::MOD, [](int64_t a, int64_t b) { return b ? a % b : a; }}, + {ConditionSet::ExpressionOp::MUL, [](int64_t a, int64_t b) { return a * b; }}, + {ConditionSet::ExpressionOp::ADD, [](int64_t a, int64_t b) { return a + b; }}, + {ConditionSet::ExpressionOp::SUB, [](int64_t a, int64_t b) { return a - b; }}, + {ConditionSet::ExpressionOp::DIV, [](int64_t a, int64_t b) { return b ? a / b : numeric_limits::max(); }} }; auto it = opMap.find(op); @@ -61,22 +61,22 @@ namespace /// Map string tokens to precedence and internal operators. const auto CS_TOKEN_CONVERSION = map{ // Infix arithmetic multiply, divide and modulo have a higher precedence than add and subtract. - { "*", ConditionSet::ExpressionOp::OP_MUL }, - { "/", ConditionSet::ExpressionOp::OP_DIV }, - { "%", ConditionSet::ExpressionOp::OP_MOD }, + { "*", ConditionSet::ExpressionOp::MUL }, + { "/", ConditionSet::ExpressionOp::DIV }, + { "%", ConditionSet::ExpressionOp::MOD }, // Infix arithmetic operators add and subtract have the same precedence. - { "+", ConditionSet::ExpressionOp::OP_ADD }, - { "-", ConditionSet::ExpressionOp::OP_SUB }, + { "+", ConditionSet::ExpressionOp::ADD }, + { "-", ConditionSet::ExpressionOp::SUB }, // Infix boolean equality operators have a lower precedence than their arithmetic counterparts. - { "==", ConditionSet::ExpressionOp::OP_EQ }, - { "!=", ConditionSet::ExpressionOp::OP_NE }, - { ">", ConditionSet::ExpressionOp::OP_GT }, - { "<", ConditionSet::ExpressionOp::OP_LT }, - { ">=", ConditionSet::ExpressionOp::OP_GE }, - { "<=", ConditionSet::ExpressionOp::OP_LE }, + { "==", ConditionSet::ExpressionOp::EQ }, + { "!=", ConditionSet::ExpressionOp::NE }, + { ">", ConditionSet::ExpressionOp::GT }, + { "<", ConditionSet::ExpressionOp::LT }, + { ">=", ConditionSet::ExpressionOp::GE }, + { "<=", ConditionSet::ExpressionOp::LE }, // Parent-type operators have a low precedence in Endless-Sky, because they are on outer parent/child sections. - { "and", ConditionSet::ExpressionOp::OP_AND }, - { "or", ConditionSet::ExpressionOp::OP_OR }, + { "and", ConditionSet::ExpressionOp::AND }, + { "or", ConditionSet::ExpressionOp::OR }, }; @@ -85,27 +85,27 @@ namespace { switch(op) { - case ConditionSet::ExpressionOp::OP_INVALID: + case ConditionSet::ExpressionOp::INVALID: return 9; - case ConditionSet::ExpressionOp::OP_LIT: - case ConditionSet::ExpressionOp::OP_VAR: + case ConditionSet::ExpressionOp::LIT: + case ConditionSet::ExpressionOp::VAR: return 8; - case ConditionSet::ExpressionOp::OP_MUL: - case ConditionSet::ExpressionOp::OP_DIV: - case ConditionSet::ExpressionOp::OP_MOD: + case ConditionSet::ExpressionOp::MUL: + case ConditionSet::ExpressionOp::DIV: + case ConditionSet::ExpressionOp::MOD: return 6; - case ConditionSet::ExpressionOp::OP_ADD: - case ConditionSet::ExpressionOp::OP_SUB: + case ConditionSet::ExpressionOp::ADD: + case ConditionSet::ExpressionOp::SUB: return 5; - case ConditionSet::ExpressionOp::OP_EQ: - case ConditionSet::ExpressionOp::OP_NE: - case ConditionSet::ExpressionOp::OP_GT: - case ConditionSet::ExpressionOp::OP_LT: - case ConditionSet::ExpressionOp::OP_GE: - case ConditionSet::ExpressionOp::OP_LE: + case ConditionSet::ExpressionOp::EQ: + case ConditionSet::ExpressionOp::NE: + case ConditionSet::ExpressionOp::GT: + case ConditionSet::ExpressionOp::LT: + case ConditionSet::ExpressionOp::GE: + case ConditionSet::ExpressionOp::LE: return 3; default: - // Precedence for OP_AND, OP_OR + // Precedence for AND, OR return 0; } } @@ -117,8 +117,8 @@ namespace if(it != CS_TOKEN_CONVERSION.end()) return it->second; - // If nothing matches, then we get the default OP_INVALID value. - return ConditionSet::ExpressionOp::OP_INVALID; + // If nothing matches, then we get the default INVALID value. + return ConditionSet::ExpressionOp::INVALID; } } @@ -135,7 +135,7 @@ ConditionSet::ConditionSet(const DataNode &node) // Construct a terminal with a literal value; ConditionSet::ConditionSet(int64_t newLiteral) { - expressionOperator = ExpressionOp::OP_LIT; + expressionOperator = ExpressionOp::LIT; literal = newLiteral; } @@ -189,7 +189,7 @@ ConditionSet &ConditionSet::operator=(const ConditionSet &other) void ConditionSet::Load(const DataNode &node) { // The top-node is always an 'and' node, without the keyword. - expressionOperator = ExpressionOp::OP_AND; + expressionOperator = ExpressionOp::AND; ParseBooleanChildren(node); } @@ -199,8 +199,8 @@ void ConditionSet::Load(const DataNode &node) void ConditionSet::Save(DataWriter &out) const { // Default should be AND, so if it is, then just write the subsets. - // If this condition got optimized beyond OP_AND, then re-add the OP_AND by writing the current condition in full. - if(expressionOperator == ExpressionOp::OP_AND) + // If this condition got optimized beyond AND, then re-add the AND by writing the current condition in full. + if(expressionOperator == ExpressionOp::AND) for(const auto &child : children) { child.SaveSubset(out); @@ -239,26 +239,26 @@ void ConditionSet::SaveSubset(DataWriter &out) const switch(expressionOperator) { - case ExpressionOp::OP_INVALID: + case ExpressionOp::INVALID: out.WriteToken("never"); break; - case ExpressionOp::OP_VAR: + case ExpressionOp::VAR: out.WriteToken(conditionName); break; - case ExpressionOp::OP_LIT: + case ExpressionOp::LIT: out.WriteToken(literal); break; - case ExpressionOp::OP_ADD: - case ExpressionOp::OP_SUB: - case ExpressionOp::OP_MUL: - case ExpressionOp::OP_DIV: - case ExpressionOp::OP_MOD: - case ExpressionOp::OP_EQ: - case ExpressionOp::OP_NE: - case ExpressionOp::OP_LE: - case ExpressionOp::OP_GE: - case ExpressionOp::OP_LT: - case ExpressionOp::OP_GT: + case ExpressionOp::ADD: + case ExpressionOp::SUB: + case ExpressionOp::MUL: + case ExpressionOp::DIV: + case ExpressionOp::MOD: + case ExpressionOp::EQ: + case ExpressionOp::NE: + case ExpressionOp::LE: + case ExpressionOp::GE: + case ExpressionOp::LT: + case ExpressionOp::GT: if(children.empty()) { out.WriteToken("never"); @@ -271,8 +271,8 @@ void ConditionSet::SaveSubset(DataWriter &out) const SaveChild(i, out); } break; - case ExpressionOp::OP_AND: - case ExpressionOp::OP_OR: + case ExpressionOp::AND: + case ExpressionOp::OR: out.Write(opTxt); out.BeginChild(); for(const auto &child : children) @@ -282,8 +282,8 @@ void ConditionSet::SaveSubset(DataWriter &out) const } out.EndChild(); break; - case ExpressionOp::OP_NOT: - case ExpressionOp::OP_HAS: + case ExpressionOp::NOT: + case ExpressionOp::HAS: if(children.empty()) { out.WriteToken("never"); @@ -303,7 +303,7 @@ void ConditionSet::SaveSubset(DataWriter &out) const void ConditionSet::MakeNever() { children.clear(); - expressionOperator = ExpressionOp::OP_LIT; + expressionOperator = ExpressionOp::LIT; literal = 0; } @@ -313,11 +313,11 @@ void ConditionSet::MakeNever() // Invalid ConditionSets are also considered empty. bool ConditionSet::IsEmpty() const { - // OP_AND is the default toplevel operator for any condition, so whenever we encounter OP_AND without any children + // AND is the default toplevel operator for any condition, so whenever we encounter AND without any children // then there was nothing under the toplevel to parse, thus the condition was empty. return - (expressionOperator == ExpressionOp::OP_AND && children.size() == 0) || - (expressionOperator == ExpressionOp::OP_INVALID); + (expressionOperator == ExpressionOp::AND && children.size() == 0) || + (expressionOperator == ExpressionOp::INVALID); } @@ -325,7 +325,7 @@ bool ConditionSet::IsEmpty() const // Check if the conditionset contains valid data bool ConditionSet::IsValid() const { - return expressionOperator != ExpressionOp::OP_INVALID; + return expressionOperator != ExpressionOp::INVALID; } @@ -342,11 +342,11 @@ int64_t ConditionSet::Evaluate(const ConditionsStore &conditionsStore) const { switch(expressionOperator) { - case ExpressionOp::OP_VAR: + case ExpressionOp::VAR: return conditionsStore.Get(conditionName); - case ExpressionOp::OP_LIT: + case ExpressionOp::LIT: return literal; - case ExpressionOp::OP_AND: + case ExpressionOp::AND: { // An empty AND section returns true. if(children.empty()) @@ -364,7 +364,7 @@ int64_t ConditionSet::Evaluate(const ConditionsStore &conditionsStore) const } return result; } - case ExpressionOp::OP_OR: + case ExpressionOp::OR: for(const ConditionSet &child : children) { int64_t childResult = child.Evaluate(conditionsStore); @@ -396,7 +396,7 @@ set ConditionSet::RelevantConditions() const { set result; // Add the name from this set, if it is a VAR type operator. - if(expressionOperator == ExpressionOp::OP_VAR) + if(expressionOperator == ExpressionOp::VAR) result.emplace(conditionName); // Add the names from the children. for(const auto &child : children) @@ -413,12 +413,12 @@ bool ConditionSet::ParseNode(const DataNode &node) { if(node.Token(0) == "and") { - expressionOperator = ExpressionOp::OP_AND; + expressionOperator = ExpressionOp::AND; return ParseBooleanChildren(node); } if(node.Token(0) == "or") { - expressionOperator = ExpressionOp::OP_OR; + expressionOperator = ExpressionOp::OR; return ParseBooleanChildren(node); } } @@ -433,7 +433,7 @@ bool ConditionSet::ParseNode(const DataNode &node) if(node.Size() > 1) return FailParse(node, "tokens found after never keyword"); - expressionOperator = ExpressionOp::OP_LIT; + expressionOperator = ExpressionOp::LIT; literal = 0; return true; } @@ -443,7 +443,7 @@ bool ConditionSet::ParseNode(const DataNode &node) return FailParse(node, "has keyword requires a single condition"); // Convert has keyword directly to the variable. - expressionOperator = ExpressionOp::OP_VAR; + expressionOperator = ExpressionOp::VAR; conditionName = node.Token(1); return true; } @@ -453,9 +453,9 @@ bool ConditionSet::ParseNode(const DataNode &node) return FailParse(node, "not keyword requires a single condition"); // Create `conditionName == 0` expression. - expressionOperator = ExpressionOp::OP_EQ; + expressionOperator = ExpressionOp::EQ; children.emplace_back(); - children.back().expressionOperator = ExpressionOp::OP_VAR; + children.back().expressionOperator = ExpressionOp::VAR; children.back().conditionName = node.Token(1); children.emplace_back(0); return true; @@ -485,7 +485,7 @@ bool ConditionSet::ParseNode(const DataNode &node, int &tokenNr) return true; // If there are more tokens, then we need to have an infix operator here. - if(!ParseFromInfix(node, tokenNr, ExpressionOp::OP_AND)) + if(!ParseFromInfix(node, tokenNr, ExpressionOp::AND)) return FailParse(); // Parsing from infix should have consumed and parsed all tokens. @@ -507,42 +507,42 @@ bool ConditionSet::Optimize(const DataNode &node) switch(expressionOperator) { - case ExpressionOp::OP_AND: - case ExpressionOp::OP_OR: + case ExpressionOp::AND: + case ExpressionOp::OR: // If we only have a single element, then replace the current OP/AND by its child. if(children.size() == 1) *this = children[0]; break; - case ExpressionOp::OP_EQ: - case ExpressionOp::OP_NE: - case ExpressionOp::OP_LE: - case ExpressionOp::OP_GE: - case ExpressionOp::OP_LT: - case ExpressionOp::OP_GT: + case ExpressionOp::EQ: + case ExpressionOp::NE: + case ExpressionOp::LE: + case ExpressionOp::GE: + case ExpressionOp::LT: + case ExpressionOp::GT: // TODO: Optimize boolean equality operators. break; - case ExpressionOp::OP_ADD: - case ExpressionOp::OP_SUB: - case ExpressionOp::OP_MUL: - case ExpressionOp::OP_DIV: - case ExpressionOp::OP_MOD: + case ExpressionOp::ADD: + case ExpressionOp::SUB: + case ExpressionOp::MUL: + case ExpressionOp::DIV: + case ExpressionOp::MOD: // TODO: Optimize arithmetic operators. break; - case ExpressionOp::OP_HAS: + case ExpressionOp::HAS: // Optimize away HAS, we can directly use the expression below it. if(children.size() == 1) *this = children[0]; break; - case ExpressionOp::OP_NOT: - case ExpressionOp::OP_LIT: - case ExpressionOp::OP_VAR: - case ExpressionOp::OP_INVALID: + case ExpressionOp::NOT: + case ExpressionOp::LIT: + case ExpressionOp::VAR: + case ExpressionOp::INVALID: break; } return returnValue; @@ -561,7 +561,7 @@ bool ConditionSet::ParseBooleanChildren(const DataNode &node) children.emplace_back(); children.back().ParseNode(child); - if(children.back().expressionOperator == ExpressionOp::OP_INVALID) + if(children.back().expressionOperator == ExpressionOp::INVALID) return FailParse(); } @@ -594,13 +594,13 @@ bool ConditionSet::ParseMini(const DataNode &node, int &tokenNr) if(node.IsNumber(tokenNr)) { - expressionOperator = ExpressionOp::OP_LIT; + expressionOperator = ExpressionOp::LIT; literal = node.Value(tokenNr); ++tokenNr; } else if(DataNode::IsConditionName(node.Token(tokenNr))) { - expressionOperator = ExpressionOp::OP_VAR; + expressionOperator = ExpressionOp::VAR; conditionName = node.Token(tokenNr); ++tokenNr; } @@ -630,7 +630,7 @@ bool ConditionSet::ParseMini(const DataNode &node, int &tokenNr) else // If there are more tokens, then we need to have an infix operator here. // Use the precedence of the AND operator, since we want to parse to the closing bracket. - if(!ParseFromInfix(node, tokenNr, ExpressionOp::OP_AND)) + if(!ParseFromInfix(node, tokenNr, ExpressionOp::AND)) return FailParse(); } return true; @@ -657,17 +657,17 @@ bool ConditionSet::ParseFromInfix(const DataNode &node, int &tokenNr, Expression ExpressionOp infixOp = ParseOperator(node.Token(tokenNr)); switch(infixOp) { - case ExpressionOp::OP_ADD: - case ExpressionOp::OP_SUB: - case ExpressionOp::OP_MUL: - case ExpressionOp::OP_DIV: - case ExpressionOp::OP_MOD: - case ExpressionOp::OP_EQ: - case ExpressionOp::OP_NE: - case ExpressionOp::OP_LE: - case ExpressionOp::OP_GE: - case ExpressionOp::OP_LT: - case ExpressionOp::OP_GT: + case ExpressionOp::ADD: + case ExpressionOp::SUB: + case ExpressionOp::MUL: + case ExpressionOp::DIV: + case ExpressionOp::MOD: + case ExpressionOp::EQ: + case ExpressionOp::NE: + case ExpressionOp::LE: + case ExpressionOp::GE: + case ExpressionOp::LT: + case ExpressionOp::GT: { if(tokenNr + 1 >= node.Size()) return FailParse(node, "expected terminal after infix operator \"" + node.Token(tokenNr) + "\""); @@ -727,7 +727,7 @@ bool ConditionSet::PushDownFull(const DataNode &node) ConditionSet ce(*this); children.clear(); children.push_back(std::move(ce)); - expressionOperator = ExpressionOp::OP_AND; + expressionOperator = ExpressionOp::AND; return true; } @@ -756,7 +756,7 @@ bool ConditionSet::PushDownLast(const DataNode &node) bool ConditionSet::FailParse() { - expressionOperator = ExpressionOp::OP_INVALID; + expressionOperator = ExpressionOp::INVALID; children.clear(); return false; } diff --git a/source/ConditionSet.h b/source/ConditionSet.h index 41b05e3dcc93..50a0646a4b99 100644 --- a/source/ConditionSet.h +++ b/source/ConditionSet.h @@ -31,6 +31,39 @@ class DataWriter; // check the values of those conditions and "evaluation" operations that can calculate an int64_t value based on the // conditions. class ConditionSet { +public: + enum class ExpressionOp { + INVALID, ///< Expression is invalid. + + // Direct access operators + VAR, ///< Direct access to condition variable, no other operations. + LIT, ///< Direct access to literal, no other operations). + + // Arithmetic operators + ADD, ///< Adds ( + ) the values from all sub-expressions. + SUB, ///< Subtracts ( - ) all later sub-expressions from the first one. + MUL, ///< Multiplies ( * ) all sub-expressions with each-other. + DIV, ///< (Integer) Divides ( / ) the first sub-expression by all later ones. + MOD, ///< Modulo ( % ) by the second and later sub-expressions on the first one. + + // Boolean equality operators, return 0 or 1 + EQ, ///< Tests for equality ( == ). + NE, ///< Tests for not equal to ( != ). + LE, ///< Tests for less than or equal to ( <= ). + GE, ///< Tests for greater than or equal to ( >= ). + LT, ///< Tests for less than ( < ). + GT, ///< Tests for greater than ( > ). + + // Boolean combination operators, return 0 or 1 + AND, ///< Boolean 'and' operator; returns 0 on first 0 subcondition, value of first sub-condition otherwise. + OR, ///< Boolean 'or' operator; returns value of first non-zero sub-condition, or zero if all are zero. + + // Single boolean operators + NOT, ///< Single boolean 'not' operator. + HAS ///< Single boolean 'has' operator. + }; + + public: ConditionSet() = default; ConditionSet(const ConditionSet &) = default; @@ -77,57 +110,20 @@ class ConditionSet { std::set RelevantConditions() const; -public: - enum class ExpressionOp - { - OP_INVALID, ///< Expression is invalid. - - // Direct access operators - OP_VAR, ///< Direct access to condition variable, no other operations. - OP_LIT, ///< Direct access to literal, no other operations). - - // Arithmetic operators - OP_ADD, ///< Adds ( + ) the values from all sub-expressions. - OP_SUB, ///< Subtracts ( - ) all later sub-expressions from the first one. - OP_MUL, ///< Multiplies ( * ) all sub-expressions with each-other. - OP_DIV, ///< (Integer) Divides ( / ) the first sub-expression by all later ones. - OP_MOD, ///< Modulo ( % ) by the second and later sub-expressions on the first one. - - // Boolean equality operators, return 0 or 1 - OP_EQ, ///< Tests for equality ( == ). - OP_NE, ///< Tests for not equal to ( != ). - OP_LE, ///< Tests for less than or equal to ( <= ). - OP_GE, ///< Tests for greater than or equal to ( >= ). - OP_LT, ///< Tests for less than ( < ). - OP_GT, ///< Tests for greater than ( > ). - - // Boolean combination operators, return 0 or 1 - OP_AND, ///< Boolean 'and' operator; returns 0 on first 0 subcondition, value of first sub-condition otherwise. - OP_OR, ///< Boolean 'or' operator; returns value of first non-zero sub-condition, or zero if all are zero. - - // Single boolean operators - OP_NOT, ///< Single boolean 'not' operator. - OP_HAS ///< Single boolean 'has' operator. - }; - - private: /// Parse a node completely into this expression; all tokens on the line and all children if there are any. bool ParseNode(const DataNode &node); - /// Parse the children under 'and'-nodes, 'or'-nodes, or the toplevel-node (which acts as and-node). The /// expression-operator should already have been set before calling this function. bool ParseBooleanChildren(const DataNode &node); - /// Parse a minimal complete expression from the tokens into the (empty) expression. /// /// @param node The node to report parse-errors on, if any occur. /// @param lineTokens Tokens to use (and pop from) for parsing. bool ParseMini(const DataNode &node, int &tokenNr); - /// Helper function to parse an infix operator and the subexpression after it. /// Should be called on ConditionSets that already have at least 1 sub-expression in them. /// @@ -135,7 +131,6 @@ class ConditionSet { /// @param lineTokens Tokens to use (and pop from) for parsing. bool ParseFromInfix(const DataNode &node, int &tokenNr, ExpressionOp parentOp); - /// Push sub-expressions and the operator from the current expression one level down into a new single /// sub-expression. /// @@ -143,22 +138,19 @@ class ConditionSet { /// @param node Node on which to report the failures (using node.PrintTrace()). bool PushDownFull(const DataNode &node); - /// Push the last sub-expression from the current expression one level down into a new sub-expression. /// /// To be used when the next infix-operator has precedence over the current operators being processed. /// @param node Node on which to report the failures (using node.PrintTrace()). bool PushDownLast(const DataNode &node); - /// Handles a failure in parsing of lower-level nodes, for higher-level nodes; - /// - Clears the sub-expressions and sets the operator to OP_INVALID. + /// - Clears the sub-expressions and sets the operator to INVALID. bool FailParse(); - /// Handles a failure in parsing; /// - Reports the failure using PrintTrace() in the given DataNode. - /// - Clears the sub-expressions and sets the operator to OP_INVALID. + /// - Clears the sub-expressions and sets the operator to INVALID. /// /// @param node Node on which to report the failures (using node.PrintTrace()). /// @param failText The reason why parsing is failing. (Will be used as output for node.PrintTrace()). @@ -171,7 +163,7 @@ class ConditionSet { /// combined using the expression operator that determines how the nested /// sets are to be combined. /// Using an `and`-operator with no sub-expressions as safe initial value. - ExpressionOp expressionOperator = ExpressionOp::OP_AND; + ExpressionOp expressionOperator = ExpressionOp::AND; /// Literal part of the expression, if this is a literal terminal. int64_t literal = 0; /// Condition variable that is used in this expression, if this is a condition variable. diff --git a/source/PlayerInfo.cpp b/source/PlayerInfo.cpp index 862c60a25111..1dc824f7327d 100644 --- a/source/PlayerInfo.cpp +++ b/source/PlayerInfo.cpp @@ -2134,10 +2134,12 @@ void PlayerInfo::AcceptJob(const Mission &mission, UI *ui) if(&*it == &mission) { cargo.AddMissionCargo(&mission); - it->Do(Mission::OFFER, *this); - it->Do(Mission::ACCEPT, *this, ui); auto spliceIt = it->IsUnique() ? missions.begin() : missions.end(); missions.splice(spliceIt, availableJobs, it); + it->Do(Mission::OFFER, *this); + it->Do(Mission::ACCEPT, *this, ui); + if(it->IsFailed(*this)) + RemoveMission(Mission::Trigger::FAIL, *it, ui); SortAvailable(); // Might not have cargo anymore, so some jobs can be sorted to end break; } diff --git a/source/image/ImageSet.cpp b/source/image/ImageSet.cpp index dd14f0877044..7a697c9d1f32 100644 --- a/source/image/ImageSet.cpp +++ b/source/image/ImageSet.cpp @@ -187,13 +187,14 @@ void ImageSet::Load() noexcept(false) // to be in separate locations on the disk. Create masks if needed. for(size_t i = 0; i < frames; ++i) { + const string fileName = "\"" + name + "\" frame #" + to_string(i); if(!buffer[0].Read(paths[0][i], i)) - Logger::LogError("Failed to read image data for \"" + name + "\" frame #" + to_string(i)); + Logger::LogError("Failed to read image data for " + fileName); else if(makeMasks) { - masks[i].Create(buffer[0], i); + masks[i].Create(buffer[0], i, fileName); if(!masks[i].IsLoaded()) - Logger::LogError("Failed to create collision mask for \"" + name + "\" frame #" + to_string(i)); + Logger::LogError("Failed to create collision mask for " + fileName); } } diff --git a/source/image/Mask.cpp b/source/image/Mask.cpp index 0ff65edf5559..2a4a911aa7a4 100644 --- a/source/image/Mask.cpp +++ b/source/image/Mask.cpp @@ -26,17 +26,17 @@ using namespace std; namespace { // Trace out outlines from an image frame. - void Trace(const ImageBuffer &image, int frame, vector> &raw) + void Trace(const ImageBuffer &image, int frame, vector> &raw, const string &fileName) { const uint32_t on = 0xFF000000; const int width = image.Width(); const int height = image.Height(); const int numPixels = width * height; const uint32_t *begin = image.Pixels() + frame * numPixels; - auto LogError = [width, height](string reason) + auto LogError = [width, height, fileName](string reason) { Logger::LogError("Unable to create mask for " + to_string(width) + "x" + to_string(height) - + " px image: " + std::move(reason)); + + " px image " + fileName + ": " + std::move(reason)); }; raw.clear(); @@ -281,13 +281,13 @@ namespace { // Construct a mask from the alpha channel of an RGBA-formatted image. -void Mask::Create(const ImageBuffer &image, int frame) +void Mask::Create(const ImageBuffer &image, int frame, const string &fileName) { outlines.clear(); radius = 0.; vector> raw; - Trace(image, frame, raw); + Trace(image, frame, raw, fileName); if(raw.empty()) return; diff --git a/source/image/Mask.h b/source/image/Mask.h index 14a35ff94105..b23584b54364 100644 --- a/source/image/Mask.h +++ b/source/image/Mask.h @@ -18,6 +18,7 @@ this program. If not, see . #include "../Angle.h" #include "../Point.h" +#include #include class ImageBuffer; @@ -32,7 +33,7 @@ class ImageBuffer; class Mask { public: // Construct a mask from the alpha channel of an RGBA-formatted image. - void Create(const ImageBuffer &image, int frame = 0); + void Create(const ImageBuffer &image, int frame, const std::string &fileName); // Check whether a mask was successfully generated from the image. bool IsLoaded() const;