* Find and parse all viewport command signs. * Fills the intro_viewport_commands vector and deletes parsed signs from the world. */
| 113 | * Fills the intro_viewport_commands vector and deletes parsed signs from the world. |
| 114 | */ |
| 115 | void ReadIntroGameViewportCommands() |
| 116 | { |
| 117 | intro_viewport_commands.clear(); |
| 118 | |
| 119 | /* Regular expression matching the commands: T, spaces, integer, spaces, flags, spaces, integer */ |
| 120 | static const std::string sign_language = "^T\\s*([0-9]+)\\s*([-+A-Z0-9]+)\\s*([0-9]+)"; |
| 121 | std::regex re(sign_language, std::regex_constants::icase); |
| 122 | |
| 123 | /* List of signs successfully parsed to delete afterwards. */ |
| 124 | std::vector<SignID> signs_to_delete; |
| 125 | |
| 126 | for (const Sign *sign : Sign::Iterate()) { |
| 127 | std::smatch match; |
| 128 | if (!std::regex_search(sign->name, match, re)) continue; |
| 129 | |
| 130 | IntroGameViewportCommand vc; |
| 131 | /* Sequence index from the first matching group. */ |
| 132 | if (auto value = ParseInteger<int>(match[1].str()); value.has_value()) { |
| 133 | vc.command_index = *value; |
| 134 | } else { |
| 135 | continue; |
| 136 | } |
| 137 | /* Sign coordinates for positioning. */ |
| 138 | vc.position = RemapCoords(sign->x, sign->y, sign->z); |
| 139 | /* Delay from the third matching group. */ |
| 140 | if (auto value = ParseInteger<uint>(match[3].str()); value.has_value()) { |
| 141 | vc.delay = *value * 1000; // milliseconds |
| 142 | } else { |
| 143 | continue; |
| 144 | } |
| 145 | |
| 146 | /* Parse flags from second matching group. */ |
| 147 | auto flags = match[2].str(); |
| 148 | StringConsumer consumer{flags}; |
| 149 | while (consumer.AnyBytesLeft()) { |
| 150 | auto c = consumer.ReadUtf8(); |
| 151 | switch (toupper(c)) { |
| 152 | case '-': vc.zoom_adjust = +1; break; |
| 153 | case '+': vc.zoom_adjust = -1; break; |
| 154 | case 'T': vc.align_v = IntroGameViewportCommand::TOP; break; |
| 155 | case 'M': vc.align_v = IntroGameViewportCommand::MIDDLE; break; |
| 156 | case 'B': vc.align_v = IntroGameViewportCommand::BOTTOM; break; |
| 157 | case 'L': vc.align_h = IntroGameViewportCommand::LEFT; break; |
| 158 | case 'C': vc.align_h = IntroGameViewportCommand::CENTRE; break; |
| 159 | case 'R': vc.align_h = IntroGameViewportCommand::RIGHT; break; |
| 160 | case 'P': vc.pan_to_next = true; break; |
| 161 | case 'V': vc.vehicle = static_cast<VehicleID>(consumer.ReadIntegerBase<uint32_t>(10, VehicleID::Invalid().base())); break; |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | /* Successfully parsed, store. */ |
| 166 | intro_viewport_commands.push_back(vc); |
| 167 | signs_to_delete.push_back(sign->index); |
| 168 | } |
| 169 | |
| 170 | /* Sort the commands by sequence index. */ |
| 171 | std::sort(intro_viewport_commands.begin(), intro_viewport_commands.end(), [](const IntroGameViewportCommand &a, const IntroGameViewportCommand &b) { return a.command_index < b.command_index; }); |
| 172 |
no test coverage detected