Merge lp:~widelands-dev/widelands/choose-attack-soldiers into lp:widelands
- choose-attack-soldiers
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 9103 | ||||||||
Proposed branch: | lp:~widelands-dev/widelands/choose-attack-soldiers | ||||||||
Merge into: | lp:widelands | ||||||||
Diff against target: |
805 lines (+440/-92) 11 files modified
src/ai/defaultai_warfare.cc (+8/-2) src/logic/game.cc (+4/-3) src/logic/game.h (+1/-1) src/logic/map_objects/tribes/militarysite.cc (+1/-0) src/logic/player.cc (+7/-10) src/logic/player.h (+1/-1) src/logic/playercommand.cc (+43/-11) src/logic/playercommand.h (+4/-4) src/wui/attack_box.cc (+287/-51) src/wui/attack_box.h (+79/-3) src/wui/fieldaction.cc (+5/-6) |
||||||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/choose-attack-soldiers | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
GunChleoc | Approve | ||
Toni Förster | Approve | ||
Review via email: mp+367041@code.launchpad.net |
Commit message
Allow the player to choose the soldiers to send in the attack box
Description of the change
The attack box contains two lists of soldiers: One for the attacking soldiers and one for the rest. Click on a soldier to move him to the other list. Ctrl-Click to move all soldiers.
If you just want to attack quickly and don´t care about soldier choice, you can still use the slider or the more/less buttons.
Note that one soldier will always remain in every militarysite. Currently the engine decides which soldier this is, and he will not be shown in the attack box. Ideally, all available soldiers should be shown, grouped by their building, and the player can then choose which soldier(s) remain(s) behind, but this would clutter up the interface too much in my opinion if there are many own militarysites, especially small ones, nearby.
bunnybot (widelandsofficial) wrote : | # |
GunChleoc (gunchleoc) : | # |
Toni Förster (stonerl) wrote : | # |
Did you remove the changes from this branch?
https:/
Benedikt Straub (nordfriese) wrote : | # |
Formatted the tooltips and replied to diff comments.
> Did you remove the changes from this branch? […]
No, I didn´t even touch the file where that code is located?
Toni Förster (stonerl) wrote : | # |
Sorry my mistake. But I do have a request. Could you add the CTRL-key behaviour to set the soldiers to max or minimum?
This is the only window left where this isn't possible.
Benedikt Straub (nordfriese) wrote : | # |
OK, I´ll add this :)
By the way, you can also Ctrl-click one of the ListOfSoldiers to put all soldiers on the other list.
Toni Förster (stonerl) wrote : | # |
Tested 9101 and it works perfect.
Just one more thing. There are only 8 Soldiers shown per row. Which means there is a huge space below the "Start Attack" button. Is it somehow possible to use this space as well and show 9 soldiers per row?
GunChleoc (gunchleoc) wrote : | # |
How about removing the slider and replacing it with some buttons next to the lists? I hate fiddling with my mouse to position it over that tiny thing.
GunChleoc (gunchleoc) wrote : | # |
Another idea: Add soldier levels to the tooltips. Good idea/bad idea?
Benedikt Straub (nordfriese) wrote : | # |
I´m in favour of keeping the slider. When you have a massive army of similar-strength soldiers and you want to attack with about half of them, the slider is the most efficient way to do this. A spinner, even with big-step buttons, would be too slow IMHO if you have really huge armies.
Since the tooltip already contains the description what happens on click and Ctrl-click, the soldier level shouldn´t be shown there as well. But I can add it to a textbox below the list like in militarysites…
Toni Förster (stonerl) wrote : | # |
> How about removing the slider and replacing it with some buttons next to the
> lists? I hate fiddling with my mouse to position it over that tiny thing.
Why not just make the slider bigger?
GunChleoc (gunchleoc) wrote : | # |
How about using Shift-click for adding all soldiers from the start of the list to the one that you are clicking?
> But I can add it to a textbox below the list like in militarysites…
Sounds good :)
Toni Förster (stonerl) wrote : | # |
Some changes I'd like to propose.
Do we need the word "soldiers" left to the slider? Shouldn't this be
obvious? We could get more space by removing text. Secondly make the
slider buttons as big as the attack button.
Here is how it could look like:
https:/
I can attach a diff to the bug report if you want me to.
Is it possible to move the "Start Attack" Button into the attack_box?
We would then get rid of the space below.
Toni Förster (stonerl) wrote : | # |
Here is another idea:
Toni Förster (stonerl) wrote : | # |
Sorry for spamming, but I think this would be the ideal solution, IMHO.
Benedikt Straub (nordfriese) wrote : | # |
Un nu de Weddervörhersaag: Een bannig dichter Hagel vun Vörslagens ;)
How do you like the one I implemented now?
Toni Förster (stonerl) wrote : | # |
> Un nu de Weddervörhersaag: Een bannig dichter Hagel vun Vörslagens ;)
:D
> How do you like the one I implemented now?
Almost awesome. :)
Some nits though:
The tooltips for the lists hide the line were the soldier’s strength is shown.
If you could change the values to the ones in the diff-comments and add the
space it would look perfect.
See the screenshot:
https:/
And don't forget to remove or comment out this line in fieldaction.cc:
static const char* const pic_attack = "images/
Toni Förster (stonerl) wrote : | # |
Oh I forgot, If you could make the slider 17 pix high instead of 20 it would look more elegant, IMHO.
Benedikt Straub (nordfriese) wrote : | # |
Implemented the changes as you suggested :)
> The tooltips for the lists hide the line were the soldier’s strength is shown.
I´m not sure how to solve this best: putting the textarea with the info on top wouldn´t look good IMHO, and the tooltip is long enough and shouldn´t contain this as well.
Perhaps I could swap the contents of the textarea and the tooltip, so the explanation what happens when clicking is displayed in the bottom line, and the info of the soldier under the mouse is shown as a tooltip that contains nothing else. What do you think?
Toni Förster (stonerl) wrote : | # |
Sorry to bother you once more :(
Could you make the slider 210px wide, please? Then we would have a maximum of 10 soldiers per row, which looks even better. :)
Regarding the tooltip. Putting them in the text area where the soldier's info are shown is too intrusive, IMHO. Can text areas also hold tooltips?
On the other hand these tooltips are quite massive. Why don't we add them to the controls-list in the encyclopedia?
Benedikt Straub (nordfriese) wrote : | # |
> Sorry to bother you once more :(
No problem :)
> Could you make the slider 210px wide, please
Done
> Can text areas also hold tooltips?
Every UI element can have a tooltip, so it could be moved to the bottom line. Problem with this is that nobody has a reason to check out a textarea´s tooltip, and since the behaviour of Shift-click is not obvious, people would notice this feature only by chance.
> On the other hand these tooltips are quite massive.
No more than those on the inputqueue buttons ;)
> Why don't we add them to the controls-list in the encyclopedia?
How many players actually read that frequently? ;) Again there´s the danger that many people won´t notice if it isn´t explained in the attackbox. Besides, this would be inconsistent as Ctrl-/Shift modifiers are explained in a tooltip for all other controls.
Toni Förster (stonerl) wrote : | # |
Hmmm, what if we added them to the "Attackers" and "Not attacking" fields? Players would definitely hover over these areas.
Benedikt Straub (nordfriese) wrote : | # |
Good idea, implemented it like this then :)
Toni Förster (stonerl) wrote : | # |
a/this
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 4932. State: passed. Details: https:/
Appveyor build 4713. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
I have pushed a commit with i18n fixes. Please merge this branch if you agree with them.
I replaced "x / y soldiers" with "x soldiers", because the total is already on the button next to the slider, and we don't need the information twice.
Benedikt Straub (nordfriese) wrote : | # |
Thanks for the fixes :)
@bunnybot merge
Toni Förster (stonerl) wrote : | # |
src/wui/
static inline std::string slider_
GunChleoc (gunchleoc) wrote : | # |
Oops. I have cleaned this up now.
Preview Diff
1 | === modified file 'src/ai/defaultai_warfare.cc' | |||
2 | --- src/ai/defaultai_warfare.cc 2019-04-09 16:43:49 +0000 | |||
3 | +++ src/ai/defaultai_warfare.cc 2019-05-11 10:49:50 +0000 | |||
4 | @@ -481,7 +481,8 @@ | |||
5 | 481 | } | 481 | } |
6 | 482 | 482 | ||
7 | 483 | // how many attack soldiers we can send? | 483 | // how many attack soldiers we can send? |
9 | 484 | int32_t attackers = player_->find_attack_soldiers(*flag); | 484 | std::vector<Soldier*> soldiers; |
10 | 485 | int32_t attackers = player_->find_attack_soldiers(*flag, &soldiers); | ||
11 | 485 | assert(attackers < 500); | 486 | assert(attackers < 500); |
12 | 486 | 487 | ||
13 | 487 | if (attackers > 5) { | 488 | if (attackers > 5) { |
14 | @@ -499,7 +500,12 @@ | |||
15 | 499 | player_number(), flag->get_position().x, flag->get_position().y, best_score, attackers, | 500 | player_number(), flag->get_position().x, flag->get_position().y, best_score, attackers, |
16 | 500 | enemy_sites[best_target].attack_counter + 1, | 501 | enemy_sites[best_target].attack_counter + 1, |
17 | 501 | (gametime - enemy_sites[best_target].last_time_attacked) / 1000); | 502 | (gametime - enemy_sites[best_target].last_time_attacked) / 1000); |
19 | 502 | game().send_player_enemyflagaction(*flag, player_number(), static_cast<uint16_t>(attackers)); | 503 | std::vector<Serial> attacking_soldiers; |
20 | 504 | for (int a = 0; a < attackers; ++a) { | ||
21 | 505 | // TODO(Nordfriese): We could now choose the soldiers we want to send | ||
22 | 506 | attacking_soldiers.push_back(soldiers[a]->serial()); | ||
23 | 507 | } | ||
24 | 508 | game().send_player_enemyflagaction(*flag, player_number(), attacking_soldiers); | ||
25 | 503 | assert(1 < | 509 | assert(1 < |
26 | 504 | player_->vision(Map::get_index(flag->get_building()->get_position(), map.get_width()))); | 510 | player_->vision(Map::get_index(flag->get_building()->get_position(), map.get_width()))); |
27 | 505 | attackers_count_ += attackers; | 511 | attackers_count_ += attackers; |
28 | 506 | 512 | ||
29 | === modified file 'src/logic/game.cc' | |||
30 | --- src/logic/game.cc 2019-05-05 18:53:14 +0000 | |||
31 | +++ src/logic/game.cc 2019-05-11 10:49:50 +0000 | |||
32 | @@ -820,10 +820,11 @@ | |||
33 | 820 | 820 | ||
34 | 821 | void Game::send_player_enemyflagaction(const Flag& flag, | 821 | void Game::send_player_enemyflagaction(const Flag& flag, |
35 | 822 | PlayerNumber const who_attacks, | 822 | PlayerNumber const who_attacks, |
37 | 823 | uint32_t const num_soldiers) { | 823 | const std::vector<Serial>& soldiers) { |
38 | 824 | if (1 < player(who_attacks) | 824 | if (1 < player(who_attacks) |
41 | 825 | .vision(Map::get_index(flag.get_building()->get_position(), map().get_width()))) | 825 | .vision(Map::get_index(flag.get_building()->get_position(), map().get_width()))) { |
42 | 826 | send_player_command(*new CmdEnemyFlagAction(get_gametime(), who_attacks, flag, num_soldiers)); | 826 | send_player_command(*new CmdEnemyFlagAction(get_gametime(), who_attacks, flag, soldiers)); |
43 | 827 | } | ||
44 | 827 | } | 828 | } |
45 | 828 | 829 | ||
46 | 829 | void Game::send_player_ship_scouting_direction(Ship& ship, WalkingDir direction) { | 830 | void Game::send_player_ship_scouting_direction(Ship& ship, WalkingDir direction) { |
47 | 830 | 831 | ||
48 | === modified file 'src/logic/game.h' | |||
49 | --- src/logic/game.h 2019-03-01 16:24:48 +0000 | |||
50 | +++ src/logic/game.h 2019-05-11 10:49:50 +0000 | |||
51 | @@ -273,7 +273,7 @@ | |||
52 | 273 | void send_player_change_training_options(TrainingSite&, TrainingAttribute, int32_t); | 273 | void send_player_change_training_options(TrainingSite&, TrainingAttribute, int32_t); |
53 | 274 | void send_player_drop_soldier(Building&, int32_t); | 274 | void send_player_drop_soldier(Building&, int32_t); |
54 | 275 | void send_player_change_soldier_capacity(Building&, int32_t); | 275 | void send_player_change_soldier_capacity(Building&, int32_t); |
56 | 276 | void send_player_enemyflagaction(const Flag&, PlayerNumber, uint32_t count); | 276 | void send_player_enemyflagaction(const Flag&, PlayerNumber, const std::vector<Serial>&); |
57 | 277 | 277 | ||
58 | 278 | void send_player_ship_scouting_direction(Ship&, WalkingDir); | 278 | void send_player_ship_scouting_direction(Ship&, WalkingDir); |
59 | 279 | void send_player_ship_construct_port(Ship&, Coords); | 279 | void send_player_ship_construct_port(Ship&, Coords); |
60 | 280 | 280 | ||
61 | === modified file 'src/logic/map_objects/tribes/militarysite.cc' | |||
62 | --- src/logic/map_objects/tribes/militarysite.cc 2019-03-01 04:19:53 +0000 | |||
63 | +++ src/logic/map_objects/tribes/militarysite.cc 2019-05-11 10:49:50 +0000 | |||
64 | @@ -374,6 +374,7 @@ | |||
65 | 374 | stationed % (capacity_ - stationed)) | 374 | stationed % (capacity_ - stationed)) |
66 | 375 | .str(); | 375 | .str(); |
67 | 376 | } else { | 376 | } else { |
68 | 377 | /** TRANSLATORS: Number of soldiers stationed at a militarysite. */ | ||
69 | 377 | *s = (boost::format(ngettext("%u soldier", "%u soldiers", stationed)) % stationed).str(); | 378 | *s = (boost::format(ngettext("%u soldier", "%u soldiers", stationed)) % stationed).str(); |
70 | 378 | } | 379 | } |
71 | 379 | } else { | 380 | } else { |
72 | 380 | 381 | ||
73 | === modified file 'src/logic/player.cc' | |||
74 | --- src/logic/player.cc 2019-05-04 10:47:44 +0000 | |||
75 | +++ src/logic/player.cc 2019-05-11 10:49:50 +0000 | |||
76 | @@ -950,20 +950,17 @@ | |||
77 | 950 | 950 | ||
78 | 951 | // TODO(unknown): Clean this mess up. The only action we really have right now is | 951 | // TODO(unknown): Clean this mess up. The only action we really have right now is |
79 | 952 | // to attack, so pretending we have more types is pointless. | 952 | // to attack, so pretending we have more types is pointless. |
82 | 953 | void Player::enemyflagaction(Flag& flag, PlayerNumber const attacker, uint32_t const count) { | 953 | void Player::enemyflagaction(Flag& flag, PlayerNumber const attacker, |
83 | 954 | if (attacker != player_number()) | 954 | const std::vector<Widelands::Soldier*>& soldiers) { |
84 | 955 | if (attacker != player_number()) { | ||
85 | 955 | log("Player (%d) is not the sender of an attack (%d)\n", attacker, player_number()); | 956 | log("Player (%d) is not the sender of an attack (%d)\n", attacker, player_number()); |
89 | 956 | else if (count == 0) | 957 | } else if (soldiers.empty()) { |
90 | 957 | log("enemyflagaction: count is 0\n"); | 958 | log("enemyflagaction: no soldiers given\n"); |
91 | 958 | else if (is_hostile(flag.owner())) { | 959 | } else if (is_hostile(flag.owner())) { |
92 | 959 | if (Building* const building = flag.get_building()) { | 960 | if (Building* const building = flag.get_building()) { |
93 | 960 | if (const AttackTarget* attack_target = building->attack_target()) { | 961 | if (const AttackTarget* attack_target = building->attack_target()) { |
94 | 961 | if (attack_target->can_be_attacked()) { | 962 | if (attack_target->can_be_attacked()) { |
100 | 962 | std::vector<Soldier*> attackers; | 963 | for (Soldier* temp_attacker : soldiers) { |
96 | 963 | find_attack_soldiers(flag, &attackers, count); | ||
97 | 964 | assert(attackers.size() <= count); | ||
98 | 965 | |||
99 | 966 | for (Soldier* temp_attacker : attackers) { | ||
101 | 967 | upcast(MilitarySite, ms, temp_attacker->get_location(egbase())); | 964 | upcast(MilitarySite, ms, temp_attacker->get_location(egbase())); |
102 | 968 | ms->send_attacker(*temp_attacker, *building); | 965 | ms->send_attacker(*temp_attacker, *building); |
103 | 969 | } | 966 | } |
104 | 970 | 967 | ||
105 | === modified file 'src/logic/player.h' | |||
106 | --- src/logic/player.h 2019-04-26 16:52:39 +0000 | |||
107 | +++ src/logic/player.h 2019-05-11 10:49:50 +0000 | |||
108 | @@ -537,7 +537,7 @@ | |||
109 | 537 | uint32_t find_attack_soldiers(Flag&, | 537 | uint32_t find_attack_soldiers(Flag&, |
110 | 538 | std::vector<Soldier*>* soldiers = nullptr, | 538 | std::vector<Soldier*>* soldiers = nullptr, |
111 | 539 | uint32_t max = std::numeric_limits<uint32_t>::max()); | 539 | uint32_t max = std::numeric_limits<uint32_t>::max()); |
113 | 540 | void enemyflagaction(Flag&, PlayerNumber attacker, uint32_t count); | 540 | void enemyflagaction(Flag&, PlayerNumber attacker, const std::vector<Widelands::Soldier*>&); |
114 | 541 | 541 | ||
115 | 542 | uint32_t casualties() const { | 542 | uint32_t casualties() const { |
116 | 543 | return casualties_; | 543 | return casualties_; |
117 | 544 | 544 | ||
118 | === modified file 'src/logic/playercommand.cc' | |||
119 | --- src/logic/playercommand.cc 2019-03-09 08:58:52 +0000 | |||
120 | +++ src/logic/playercommand.cc 2019-05-11 10:49:50 +0000 | |||
121 | @@ -1589,7 +1589,11 @@ | |||
122 | 1589 | des.unsigned_8(); | 1589 | des.unsigned_8(); |
123 | 1590 | serial = des.unsigned_32(); | 1590 | serial = des.unsigned_32(); |
124 | 1591 | des.unsigned_8(); | 1591 | des.unsigned_8(); |
126 | 1592 | number = des.unsigned_8(); | 1592 | const uint32_t number = des.unsigned_32(); |
127 | 1593 | soldiers.clear(); | ||
128 | 1594 | for (uint32_t i = 0; i < number; ++i) { | ||
129 | 1595 | soldiers.push_back(des.unsigned_32()); | ||
130 | 1596 | } | ||
131 | 1593 | } | 1597 | } |
132 | 1594 | 1598 | ||
133 | 1595 | void CmdEnemyFlagAction::execute(Game& game) { | 1599 | void CmdEnemyFlagAction::execute(Game& game) { |
134 | @@ -1597,16 +1601,21 @@ | |||
135 | 1597 | 1601 | ||
136 | 1598 | if (upcast(Flag, flag, game.objects().get_object(serial))) { | 1602 | if (upcast(Flag, flag, game.objects().get_object(serial))) { |
137 | 1599 | log("Cmd_EnemyFlagAction::execute player(%u): flag->owner(%d) " | 1603 | log("Cmd_EnemyFlagAction::execute player(%u): flag->owner(%d) " |
140 | 1600 | "number=%u\n", | 1604 | "number=%" PRIuS "\n", |
141 | 1601 | player->player_number(), flag->owner().player_number(), number); | 1605 | player->player_number(), flag->owner().player_number(), soldiers.size()); |
142 | 1602 | 1606 | ||
143 | 1603 | if (const Building* const building = flag->get_building()) { | 1607 | if (const Building* const building = flag->get_building()) { |
144 | 1604 | if (player->is_hostile(flag->owner()) && | 1608 | if (player->is_hostile(flag->owner()) && |
148 | 1605 | 1 < player->vision(Map::get_index(building->get_position(), game.map().get_width()))) | 1609 | 1 < player->vision(Map::get_index(building->get_position(), game.map().get_width()))) { |
149 | 1606 | player->enemyflagaction(*flag, sender(), number); | 1610 | std::vector<Soldier*> result; |
150 | 1607 | else | 1611 | for (Serial s : soldiers) { |
151 | 1612 | result.push_back(dynamic_cast<Soldier*>(game.objects().get_object(s))); | ||
152 | 1613 | } | ||
153 | 1614 | player->enemyflagaction(*flag, sender(), result); | ||
154 | 1615 | } else { | ||
155 | 1608 | log("Cmd_EnemyFlagAction::execute: ERROR: wrong player target not " | 1616 | log("Cmd_EnemyFlagAction::execute: ERROR: wrong player target not " |
156 | 1609 | "seen or not hostile.\n"); | 1617 | "seen or not hostile.\n"); |
157 | 1618 | } | ||
158 | 1610 | } | 1619 | } |
159 | 1611 | } | 1620 | } |
160 | 1612 | } | 1621 | } |
161 | @@ -1617,20 +1626,40 @@ | |||
162 | 1617 | ser.unsigned_8(1); | 1626 | ser.unsigned_8(1); |
163 | 1618 | ser.unsigned_32(serial); | 1627 | ser.unsigned_32(serial); |
164 | 1619 | ser.unsigned_8(sender()); | 1628 | ser.unsigned_8(sender()); |
166 | 1620 | ser.unsigned_8(number); | 1629 | ser.unsigned_32(soldiers.size()); |
167 | 1630 | for (Serial s : soldiers) { | ||
168 | 1631 | ser.unsigned_32(s); | ||
169 | 1632 | } | ||
170 | 1621 | } | 1633 | } |
171 | 1622 | 1634 | ||
173 | 1623 | constexpr uint16_t kCurrentPacketVersionCmdEnemyFlagAction = 3; | 1635 | constexpr uint16_t kCurrentPacketVersionCmdEnemyFlagAction = 4; |
174 | 1624 | 1636 | ||
175 | 1625 | void CmdEnemyFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { | 1637 | void CmdEnemyFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { |
176 | 1626 | try { | 1638 | try { |
177 | 1627 | const uint16_t packet_version = fr.unsigned_16(); | 1639 | const uint16_t packet_version = fr.unsigned_16(); |
179 | 1628 | if (packet_version == kCurrentPacketVersionCmdEnemyFlagAction) { | 1640 | if (packet_version <= kCurrentPacketVersionCmdEnemyFlagAction && packet_version >= 3) { |
180 | 1629 | PlayerCommand::read(fr, egbase, mol); | 1641 | PlayerCommand::read(fr, egbase, mol); |
181 | 1630 | fr.unsigned_8(); | 1642 | fr.unsigned_8(); |
182 | 1631 | serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol); | 1643 | serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol); |
183 | 1632 | fr.unsigned_8(); | 1644 | fr.unsigned_8(); |
185 | 1633 | number = fr.unsigned_8(); | 1645 | |
186 | 1646 | soldiers.clear(); | ||
187 | 1647 | if (packet_version == kCurrentPacketVersionCmdEnemyFlagAction) { | ||
188 | 1648 | const uint32_t number = fr.unsigned_32(); | ||
189 | 1649 | for (uint32_t i = 0; i < number; ++i) { | ||
190 | 1650 | soldiers.push_back(mol.get<Soldier>(fr.unsigned_32()).serial()); | ||
191 | 1651 | } | ||
192 | 1652 | } else { | ||
193 | 1653 | const uint8_t number = fr.unsigned_8(); | ||
194 | 1654 | upcast(Flag, flag, egbase.objects().get_object(serial)); | ||
195 | 1655 | assert(flag); | ||
196 | 1656 | std::vector<Soldier*> result; | ||
197 | 1657 | egbase.get_player(sender())->find_attack_soldiers(*flag, &result, number); | ||
198 | 1658 | assert(result.size() == number); | ||
199 | 1659 | for (const auto& s : result) { | ||
200 | 1660 | soldiers.push_back(s->serial()); | ||
201 | 1661 | } | ||
202 | 1662 | } | ||
203 | 1634 | } else { | 1663 | } else { |
204 | 1635 | throw UnhandledVersionError( | 1664 | throw UnhandledVersionError( |
205 | 1636 | "CmdEnemyFlagAction", packet_version, kCurrentPacketVersionCmdEnemyFlagAction); | 1665 | "CmdEnemyFlagAction", packet_version, kCurrentPacketVersionCmdEnemyFlagAction); |
206 | @@ -1653,7 +1682,10 @@ | |||
207 | 1653 | 1682 | ||
208 | 1654 | // Now param | 1683 | // Now param |
209 | 1655 | fw.unsigned_8(sender()); | 1684 | fw.unsigned_8(sender()); |
211 | 1656 | fw.unsigned_8(number); | 1685 | fw.unsigned_32(soldiers.size()); |
212 | 1686 | for (Serial s : soldiers) { | ||
213 | 1687 | fw.unsigned_32(mos.get_object_file_index(*egbase.objects().get_object(s))); | ||
214 | 1688 | } | ||
215 | 1657 | } | 1689 | } |
216 | 1658 | 1690 | ||
217 | 1659 | /*** struct PlayerMessageCommand ***/ | 1691 | /*** struct PlayerMessageCommand ***/ |
218 | 1660 | 1692 | ||
219 | === modified file 'src/logic/playercommand.h' | |||
220 | --- src/logic/playercommand.h 2019-02-23 11:00:49 +0000 | |||
221 | +++ src/logic/playercommand.h 2019-05-11 10:49:50 +0000 | |||
222 | @@ -733,10 +733,10 @@ | |||
223 | 733 | }; | 733 | }; |
224 | 734 | 734 | ||
225 | 735 | struct CmdEnemyFlagAction : public PlayerCommand { | 735 | struct CmdEnemyFlagAction : public PlayerCommand { |
227 | 736 | CmdEnemyFlagAction() : PlayerCommand(), serial(0), number(0) { | 736 | CmdEnemyFlagAction() : PlayerCommand(), serial(0) { |
228 | 737 | } // For savegame loading | 737 | } // For savegame loading |
231 | 738 | CmdEnemyFlagAction(uint32_t t, int32_t p, const Flag& f, uint32_t num) | 738 | CmdEnemyFlagAction(uint32_t t, int32_t p, const Flag& f, const std::vector<Serial>& s) |
232 | 739 | : PlayerCommand(t, p), serial(f.serial()), number(num) { | 739 | : PlayerCommand(t, p), serial(f.serial()), soldiers(s) { |
233 | 740 | } | 740 | } |
234 | 741 | 741 | ||
235 | 742 | // Write these commands to a file (for savegames) | 742 | // Write these commands to a file (for savegames) |
236 | @@ -754,7 +754,7 @@ | |||
237 | 754 | 754 | ||
238 | 755 | private: | 755 | private: |
239 | 756 | Serial serial; | 756 | Serial serial; |
241 | 757 | uint8_t number; | 757 | std::vector<Serial> soldiers; |
242 | 758 | }; | 758 | }; |
243 | 759 | 759 | ||
244 | 760 | /// Abstract base for commands about a message. | 760 | /// Abstract base for commands about a message. |
245 | 761 | 761 | ||
246 | === modified file 'src/wui/attack_box.cc' | |||
247 | --- src/wui/attack_box.cc 2019-02-23 11:00:49 +0000 | |||
248 | +++ src/wui/attack_box.cc 2019-05-11 10:49:50 +0000 | |||
249 | @@ -30,7 +30,7 @@ | |||
250 | 30 | #include "graphic/text_constants.h" | 30 | #include "graphic/text_constants.h" |
251 | 31 | #include "logic/map_objects/tribes/soldier.h" | 31 | #include "logic/map_objects/tribes/soldier.h" |
252 | 32 | 32 | ||
254 | 33 | constexpr int32_t kUpdateTimeInGametimeMs = 1000; // 1 second, gametime | 33 | constexpr int32_t kUpdateTimeInGametimeMs = 500; // half a second, gametime |
255 | 34 | 34 | ||
256 | 35 | AttackBox::AttackBox(UI::Panel* parent, | 35 | AttackBox::AttackBox(UI::Panel* parent, |
257 | 36 | Widelands::Player* player, | 36 | Widelands::Player* player, |
258 | @@ -45,19 +45,24 @@ | |||
259 | 45 | init(); | 45 | init(); |
260 | 46 | } | 46 | } |
261 | 47 | 47 | ||
263 | 48 | uint32_t AttackBox::get_max_attackers() { | 48 | std::vector<Widelands::Soldier*> AttackBox::get_max_attackers() { |
264 | 49 | assert(player_); | 49 | assert(player_); |
265 | 50 | 50 | ||
266 | 51 | if (upcast(Building, building, map_.get_immovable(*node_coordinates_))) { | 51 | if (upcast(Building, building, map_.get_immovable(*node_coordinates_))) { |
271 | 52 | if (player_->vision(map_.get_index(building->get_position(), map_.get_width())) <= 1) { | 52 | if (player_->vision(map_.get_index(building->get_position(), map_.get_width())) > 1) { |
272 | 53 | // Player can't see the buildings door, so it can't be attacked | 53 | std::vector<Widelands::Soldier*> v; |
273 | 54 | // This is the same check as done later on in send_player_enemyflagaction() | 54 | // TODO(Nordfriese): This method decides by itself which soldier remains in the building. |
274 | 55 | return 0; | 55 | // This soldier will not show up in the result vector. Perhaps we should show all |
275 | 56 | // available soldiers, grouped by building, so the player can choose between all soldiers | ||
276 | 57 | // knowing that at least one of each group will have to stay at home. However, this | ||
277 | 58 | // could clutter up the screen a lot. Especially if you have many small buildings. | ||
278 | 59 | player_->find_attack_soldiers(building->base_flag(), &v); | ||
279 | 60 | return v; | ||
280 | 56 | } | 61 | } |
283 | 57 | 62 | // Player can't see the buildings door, so it can't be attacked | |
284 | 58 | return player_->find_attack_soldiers(building->base_flag()); | 63 | // This is the same check as done later on in send_player_enemyflagaction() |
285 | 59 | } | 64 | } |
287 | 60 | return 0; | 65 | return std::vector<Widelands::Soldier*>(); |
288 | 61 | } | 66 | } |
289 | 62 | 67 | ||
290 | 63 | std::unique_ptr<UI::HorizontalSlider> AttackBox::add_slider(UI::Box& parent, | 68 | std::unique_ptr<UI::HorizontalSlider> AttackBox::add_slider(UI::Box& parent, |
291 | @@ -86,7 +91,7 @@ | |||
292 | 86 | void (AttackBox::*fn)(), | 91 | void (AttackBox::*fn)(), |
293 | 87 | const std::string& tooltip_text) { | 92 | const std::string& tooltip_text) { |
294 | 88 | std::unique_ptr<UI::Button> button(new UI::Button( | 93 | std::unique_ptr<UI::Button> button(new UI::Button( |
296 | 89 | &parent, text, 8, 8, 26, 26, UI::ButtonStyle::kWuiPrimary, text, tooltip_text)); | 94 | &parent, text, 8, 8, 34, 34, UI::ButtonStyle::kWuiPrimary, text, tooltip_text)); |
297 | 90 | button->sigclicked.connect(boost::bind(fn, boost::ref(*this))); | 95 | button->sigclicked.connect(boost::bind(fn, boost::ref(*this))); |
298 | 91 | parent.add(button.get()); | 96 | parent.add(button.get()); |
299 | 92 | return button; | 97 | return button; |
300 | @@ -96,31 +101,78 @@ | |||
301 | 96 | * Update available soldiers | 101 | * Update available soldiers |
302 | 97 | */ | 102 | */ |
303 | 98 | void AttackBox::think() { | 103 | void AttackBox::think() { |
308 | 99 | const int32_t gametime = player_->egbase().get_gametime(); | 104 | if ((player_->egbase().get_gametime() - lastupdate_) > kUpdateTimeInGametimeMs) { |
309 | 100 | if ((gametime - lastupdate_) > kUpdateTimeInGametimeMs) { | 105 | update_attack(false); |
306 | 101 | update_attack(); | ||
307 | 102 | lastupdate_ = gametime; | ||
310 | 103 | } | 106 | } |
311 | 104 | } | 107 | } |
312 | 105 | 108 | ||
314 | 106 | void AttackBox::update_attack() { | 109 | static inline std::string slider_heading(uint32_t num_attackers) { |
315 | 110 | /** TRANSLATORS: Number of soldiers that should attack. Used in Attack box. */ | ||
316 | 111 | return (boost::format(ngettext("%u soldier", "%u soldiers", num_attackers)) % num_attackers).str(); | ||
317 | 112 | } | ||
318 | 113 | |||
319 | 114 | void AttackBox::update_attack(bool action_on_panel) { | ||
320 | 115 | lastupdate_ = player_->egbase().get_gametime(); | ||
321 | 116 | |||
322 | 107 | assert(soldiers_slider_.get()); | 117 | assert(soldiers_slider_.get()); |
323 | 108 | assert(soldiers_text_.get()); | 118 | assert(soldiers_text_.get()); |
324 | 109 | assert(less_soldiers_.get()); | 119 | assert(less_soldiers_.get()); |
325 | 110 | assert(more_soldiers_.get()); | 120 | assert(more_soldiers_.get()); |
329 | 111 | 121 | assert(attacking_soldiers_.get()); | |
330 | 112 | int32_t max_attackers = get_max_attackers(); | 122 | assert(remaining_soldiers_.get()); |
331 | 113 | 123 | ||
332 | 124 | std::vector<Widelands::Soldier*> all_attackers = get_max_attackers(); | ||
333 | 125 | const int max_attackers = all_attackers.size(); | ||
334 | 126 | |||
335 | 127 | // Update number of available soldiers | ||
336 | 114 | if (soldiers_slider_->get_max_value() != max_attackers) { | 128 | if (soldiers_slider_->get_max_value() != max_attackers) { |
337 | 115 | soldiers_slider_->set_max_value(max_attackers); | 129 | soldiers_slider_->set_max_value(max_attackers); |
338 | 116 | } | 130 | } |
339 | 117 | 131 | ||
340 | 132 | // Add new soldiers and remove missing soldiers to/from the list | ||
341 | 133 | for (const auto& s : all_attackers) { | ||
342 | 134 | if (!attacking_soldiers_->contains(s) && !remaining_soldiers_->contains(s)) { | ||
343 | 135 | remaining_soldiers_->add(s); | ||
344 | 136 | } | ||
345 | 137 | } | ||
346 | 138 | for (const auto& s : remaining_soldiers_->get_soldiers()) { | ||
347 | 139 | if (std::find(all_attackers.begin(), all_attackers.end(), s) == all_attackers.end()) { | ||
348 | 140 | remaining_soldiers_->remove(s); | ||
349 | 141 | } | ||
350 | 142 | } | ||
351 | 143 | for (const auto& s : attacking_soldiers_->get_soldiers()) { | ||
352 | 144 | if (std::find(all_attackers.begin(), all_attackers.end(), s) == all_attackers.end()) { | ||
353 | 145 | attacking_soldiers_->remove(s); | ||
354 | 146 | } | ||
355 | 147 | } | ||
356 | 148 | |||
357 | 149 | if (action_on_panel) { | ||
358 | 150 | // The player clicked on soldiers in the list – update slider | ||
359 | 151 | soldiers_slider_->set_value(attacking_soldiers_->count_soldiers()); | ||
360 | 152 | } else { | ||
361 | 153 | // The slider was moved or we were called from think() – shift lacking/extra soldiers between the lists | ||
362 | 154 | const int32_t lacking = soldiers_slider_->get_value() - attacking_soldiers_->count_soldiers(); | ||
363 | 155 | if (lacking > 0) { | ||
364 | 156 | for (int32_t i = 0; i < lacking; ++i) { | ||
365 | 157 | const Widelands::Soldier* s = remaining_soldiers_->get_soldier(); | ||
366 | 158 | remaining_soldiers_->remove(s); | ||
367 | 159 | attacking_soldiers_->add(s); | ||
368 | 160 | } | ||
369 | 161 | } else if (lacking < 0) { | ||
370 | 162 | for (int32_t i = 0; i > lacking; --i) { | ||
371 | 163 | const Widelands::Soldier* s = attacking_soldiers_->get_soldier(); | ||
372 | 164 | attacking_soldiers_->remove(s); | ||
373 | 165 | remaining_soldiers_->add(s); | ||
374 | 166 | } | ||
375 | 167 | } | ||
376 | 168 | } | ||
377 | 169 | |||
378 | 170 | // Update slider, buttons and texts | ||
379 | 118 | soldiers_slider_->set_enabled(max_attackers > 0); | 171 | soldiers_slider_->set_enabled(max_attackers > 0); |
380 | 119 | more_soldiers_->set_enabled(max_attackers > soldiers_slider_->get_value()); | 172 | more_soldiers_->set_enabled(max_attackers > soldiers_slider_->get_value()); |
381 | 120 | less_soldiers_->set_enabled(soldiers_slider_->get_value() > 0); | 173 | less_soldiers_->set_enabled(soldiers_slider_->get_value() > 0); |
385 | 121 | soldiers_text_->set_text( | 174 | |
386 | 122 | /** TRANSLATORS: %1% of %2% soldiers. Used in Attack box. */ | 175 | soldiers_text_->set_text(slider_heading(soldiers_slider_->get_value())); |
384 | 123 | (boost::format(_("%1% / %2%")) % soldiers_slider_->get_value() % max_attackers).str()); | ||
387 | 124 | 176 | ||
388 | 125 | more_soldiers_->set_title(std::to_string(max_attackers)); | 177 | more_soldiers_->set_title(std::to_string(max_attackers)); |
389 | 126 | } | 178 | } |
390 | @@ -128,48 +180,232 @@ | |||
391 | 128 | void AttackBox::init() { | 180 | void AttackBox::init() { |
392 | 129 | assert(node_coordinates_); | 181 | assert(node_coordinates_); |
393 | 130 | 182 | ||
405 | 131 | uint32_t max_attackers = get_max_attackers(); | 183 | std::vector<Widelands::Soldier*> all_attackers = get_max_attackers(); |
406 | 132 | 184 | const size_t max_attackers = all_attackers.size(); | |
407 | 133 | UI::Box& linebox = *new UI::Box(this, 0, 0, UI::Box::Horizontal); | 185 | |
408 | 134 | add(&linebox); | 186 | UI::Box& mainbox = *new UI::Box(this, 0, 0, UI::Box::Vertical); |
409 | 135 | add_text(linebox, _("Soldiers:")); | 187 | add(&mainbox); |
410 | 136 | linebox.add_space(8); | 188 | |
411 | 137 | 189 | UI::Box& linebox = *new UI::Box(&mainbox, 0, 0, UI::Box::Horizontal); | |
412 | 138 | less_soldiers_ = | 190 | mainbox.add(&linebox); |
413 | 139 | add_button(linebox, "0", &AttackBox::send_less_soldiers, _("Send less soldiers")); | 191 | |
414 | 140 | 192 | less_soldiers_ = add_button(linebox, "0", &AttackBox::send_less_soldiers, | |
415 | 141 | // Spliter of soldiers | 193 | _("Send less soldiers. Hold down Ctrl to send no soldiers")); |
416 | 194 | |||
417 | 142 | UI::Box& columnbox = *new UI::Box(&linebox, 0, 0, UI::Box::Vertical); | 195 | UI::Box& columnbox = *new UI::Box(&linebox, 0, 0, UI::Box::Vertical); |
418 | 143 | linebox.add(&columnbox); | 196 | linebox.add(&columnbox); |
419 | 144 | 197 | ||
425 | 145 | const std::string attack_string = | 198 | soldiers_text_.reset(&add_text(columnbox, slider_heading(max_attackers > 0 ? 1 : 0), |
426 | 146 | (boost::format(_("%1% / %2%")) % (max_attackers > 0 ? 1 : 0) % max_attackers).str(); | 199 | UI::Align::kCenter, UI_FONT_SIZE_ULTRASMALL)); |
422 | 147 | |||
423 | 148 | soldiers_text_.reset( | ||
424 | 149 | &add_text(columnbox, attack_string, UI::Align::kCenter, UI_FONT_SIZE_ULTRASMALL)); | ||
427 | 150 | 200 | ||
428 | 151 | soldiers_slider_ = add_slider( | 201 | soldiers_slider_ = add_slider( |
434 | 152 | columnbox, 100, 10, 0, max_attackers, max_attackers > 0 ? 1 : 0, _("Number of soldiers")); | 202 | columnbox, 210, 17, 0, max_attackers, max_attackers > 0 ? 1 : 0, _("Number of soldiers")); |
435 | 153 | 203 | soldiers_slider_->changed.connect([this]() { update_attack(false); }); | |
436 | 154 | soldiers_slider_->changed.connect(boost::bind(&AttackBox::update_attack, this)); | 204 | |
437 | 155 | more_soldiers_ = add_button(linebox, std::to_string(max_attackers), | 205 | more_soldiers_ = add_button(linebox, std::to_string(max_attackers), &AttackBox::send_more_soldiers, |
438 | 156 | &AttackBox::send_more_soldiers, _("Send more soldiers")); | 206 | _("Send more soldiers. Hold down Ctrl to send as many soldiers as possible")); |
439 | 207 | linebox.add_space(8); | ||
440 | 208 | |||
441 | 209 | attack_button_.reset(new UI::Button(&linebox, "attack", 8, 8, 34, 34, UI::ButtonStyle::kWuiPrimary, | ||
442 | 210 | g_gr->images().get("images/wui/buildings/menu_attack.png"), _("Start attack"))); | ||
443 | 211 | linebox.add(attack_button_.get()); | ||
444 | 212 | |||
445 | 213 | attacking_soldiers_.reset(new ListOfSoldiers(&mainbox, this, 0, 0, 30, 30)); | ||
446 | 214 | remaining_soldiers_.reset(new ListOfSoldiers(&mainbox, this, 0, 0, 30, 30)); | ||
447 | 215 | attacking_soldiers_->set_complement(remaining_soldiers_.get()); | ||
448 | 216 | remaining_soldiers_->set_complement(attacking_soldiers_.get()); | ||
449 | 217 | for (const auto& s : all_attackers) { | ||
450 | 218 | remaining_soldiers_->add(s); | ||
451 | 219 | } | ||
452 | 220 | |||
453 | 221 | boost::format tooltip_format("%s<br><p><font size=%d bold=0>%s<br>%s</font></p>"); | ||
454 | 222 | { | ||
455 | 223 | UI::Textarea& txt = add_text(mainbox, _("Attackers:")); | ||
456 | 224 | // Needed so we can get tooltips | ||
457 | 225 | txt.set_handle_mouse(true); | ||
458 | 226 | txt.set_tooltip((tooltip_format | ||
459 | 227 | % _("Click on a soldier to remove him from the list of attackers") | ||
460 | 228 | % UI_FONT_SIZE_MESSAGE | ||
461 | 229 | % _("Hold down Ctrl to remove all soldiers from the list") | ||
462 | 230 | % _("Hold down Shift to remove all soldiers up to the one you’re pointing at")) | ||
463 | 231 | .str()); | ||
464 | 232 | mainbox.add(attacking_soldiers_.get(), UI::Box::Resizing::kFullSize); | ||
465 | 233 | } | ||
466 | 234 | |||
467 | 235 | { | ||
468 | 236 | UI::Textarea& txt = add_text(mainbox, _("Not attacking:")); | ||
469 | 237 | txt.set_handle_mouse(true); | ||
470 | 238 | txt.set_tooltip((tooltip_format | ||
471 | 239 | % _("Click on a soldier to add him to the list of attackers") | ||
472 | 240 | % UI_FONT_SIZE_MESSAGE | ||
473 | 241 | % _("Hold down Ctrl to add all soldiers to the list") | ||
474 | 242 | % _("Hold down Shift to add all soldiers up to the one you’re pointing at")) | ||
475 | 243 | .str()); | ||
476 | 244 | mainbox.add(remaining_soldiers_.get(), UI::Box::Resizing::kFullSize); | ||
477 | 245 | } | ||
478 | 246 | |||
479 | 247 | current_soldier_stats_.reset(new UI::Textarea(&mainbox, "", UI::Align::kCenter)); | ||
480 | 248 | mainbox.add(current_soldier_stats_.get(), UI::Box::Resizing::kFullSize, UI::Align::kCenter); | ||
481 | 157 | 249 | ||
482 | 158 | soldiers_slider_->set_enabled(max_attackers > 0); | 250 | soldiers_slider_->set_enabled(max_attackers > 0); |
483 | 159 | more_soldiers_->set_enabled(max_attackers > 0); | 251 | more_soldiers_->set_enabled(max_attackers > 0); |
484 | 160 | less_soldiers_->set_enabled(max_attackers > 0); | ||
485 | 161 | } | 252 | } |
486 | 162 | 253 | ||
487 | 163 | void AttackBox::send_less_soldiers() { | 254 | void AttackBox::send_less_soldiers() { |
488 | 164 | assert(soldiers_slider_.get()); | 255 | assert(soldiers_slider_.get()); |
490 | 165 | soldiers_slider_->set_value(soldiers_slider_->get_value() - 1); | 256 | soldiers_slider_->set_value((SDL_GetModState() & KMOD_CTRL) ? 0 : soldiers_slider_->get_value() - 1); |
491 | 166 | } | 257 | } |
492 | 167 | 258 | ||
493 | 168 | void AttackBox::send_more_soldiers() { | 259 | void AttackBox::send_more_soldiers() { |
501 | 169 | soldiers_slider_->set_value(soldiers_slider_->get_value() + 1); | 260 | soldiers_slider_->set_value((SDL_GetModState() & KMOD_CTRL) ? soldiers_slider_->get_max_value() : |
502 | 170 | } | 261 | soldiers_slider_->get_value() + 1); |
503 | 171 | 262 | } | |
504 | 172 | uint32_t AttackBox::soldiers() const { | 263 | |
505 | 173 | assert(soldiers_slider_.get()); | 264 | size_t AttackBox::count_soldiers() const { |
506 | 174 | return soldiers_slider_->get_value(); | 265 | return attacking_soldiers_->count_soldiers(); |
507 | 175 | } | 266 | } |
508 | 267 | |||
509 | 268 | std::vector<Widelands::Serial> AttackBox::soldiers() const { | ||
510 | 269 | std::vector<Widelands::Serial> result; | ||
511 | 270 | for (const auto& s : attacking_soldiers_->get_soldiers()) { | ||
512 | 271 | result.push_back(s->serial()); | ||
513 | 272 | } | ||
514 | 273 | return result; | ||
515 | 274 | } | ||
516 | 275 | |||
517 | 276 | constexpr int kSoldierIconWidth = 32; | ||
518 | 277 | constexpr int kSoldierIconHeight = 30; | ||
519 | 278 | |||
520 | 279 | AttackBox::ListOfSoldiers::ListOfSoldiers(UI::Panel* const parent, | ||
521 | 280 | AttackBox* parent_box, | ||
522 | 281 | int32_t const x, | ||
523 | 282 | int32_t const y, | ||
524 | 283 | int const w, | ||
525 | 284 | int const h, | ||
526 | 285 | bool restrict_rows) | ||
527 | 286 | : UI::Panel(parent, x, y, w, h), | ||
528 | 287 | restricted_row_number_(restrict_rows), | ||
529 | 288 | attack_box_(parent_box) { | ||
530 | 289 | update_desired_size(); | ||
531 | 290 | } | ||
532 | 291 | |||
533 | 292 | bool AttackBox::ListOfSoldiers::handle_mousepress(uint8_t btn, int32_t x, int32_t y) { | ||
534 | 293 | if (btn != SDL_BUTTON_LEFT || !other_) { | ||
535 | 294 | return UI::Panel::handle_mousepress(btn, x, y); | ||
536 | 295 | } | ||
537 | 296 | if (SDL_GetModState() & KMOD_CTRL) { | ||
538 | 297 | for (const auto& s : get_soldiers()) { | ||
539 | 298 | remove(s); | ||
540 | 299 | other_->add(s); | ||
541 | 300 | } | ||
542 | 301 | } else { | ||
543 | 302 | const Widelands::Soldier* soldier = soldier_at(x, y); | ||
544 | 303 | if (!soldier) { | ||
545 | 304 | return UI::Panel::handle_mousepress(btn, x, y); | ||
546 | 305 | } | ||
547 | 306 | if (SDL_GetModState() & KMOD_SHIFT) { | ||
548 | 307 | for (const auto& s : get_soldiers()) { | ||
549 | 308 | remove(s); | ||
550 | 309 | other_->add(s); | ||
551 | 310 | if (s == soldier) { | ||
552 | 311 | break; | ||
553 | 312 | } | ||
554 | 313 | } | ||
555 | 314 | } else { | ||
556 | 315 | remove(soldier); | ||
557 | 316 | other_->add(soldier); | ||
558 | 317 | } | ||
559 | 318 | } | ||
560 | 319 | attack_box_->update_attack(true); | ||
561 | 320 | return true; | ||
562 | 321 | } | ||
563 | 322 | |||
564 | 323 | void AttackBox::ListOfSoldiers::handle_mousein(bool) { | ||
565 | 324 | attack_box_->set_soldier_info_text(); | ||
566 | 325 | } | ||
567 | 326 | |||
568 | 327 | bool AttackBox::ListOfSoldiers::handle_mousemove(uint8_t, int32_t x, int32_t y, int32_t, int32_t) { | ||
569 | 328 | if (const Widelands::Soldier* soldier = soldier_at(x, y)) { | ||
570 | 329 | attack_box_->set_soldier_info_text( | ||
571 | 330 | (boost::format(_("HP: %1$u/%2$u AT: %3$u/%4$u DE: %5$u/%6$u EV: %7$u/%8$u")) % | ||
572 | 331 | soldier->get_health_level() % soldier->descr().get_max_health_level() % | ||
573 | 332 | soldier->get_attack_level() % soldier->descr().get_max_attack_level() % | ||
574 | 333 | soldier->get_defense_level() % soldier->descr().get_max_defense_level() % | ||
575 | 334 | soldier->get_evade_level() % soldier->descr().get_max_evade_level()) | ||
576 | 335 | .str()); | ||
577 | 336 | } else { | ||
578 | 337 | attack_box_->set_soldier_info_text(); | ||
579 | 338 | } | ||
580 | 339 | return true; | ||
581 | 340 | } | ||
582 | 341 | |||
583 | 342 | Widelands::Extent AttackBox::ListOfSoldiers::size() const { | ||
584 | 343 | const size_t nr_soldiers = count_soldiers(); | ||
585 | 344 | uint32_t rows = nr_soldiers / current_size_; | ||
586 | 345 | if (nr_soldiers == 0 || rows * current_size_ < nr_soldiers) { | ||
587 | 346 | ++rows; | ||
588 | 347 | } | ||
589 | 348 | if (restricted_row_number_) { | ||
590 | 349 | return Widelands::Extent(rows, current_size_); | ||
591 | 350 | } else { | ||
592 | 351 | return Widelands::Extent(current_size_, rows); | ||
593 | 352 | } | ||
594 | 353 | } | ||
595 | 354 | |||
596 | 355 | void AttackBox::ListOfSoldiers::update_desired_size() { | ||
597 | 356 | current_size_ = std::max(1, restricted_row_number_ ? get_h() / kSoldierIconHeight : get_w() / kSoldierIconWidth); | ||
598 | 357 | const Widelands::Extent e = size(); | ||
599 | 358 | set_desired_size(e.w * kSoldierIconWidth, e.h * kSoldierIconHeight); | ||
600 | 359 | } | ||
601 | 360 | |||
602 | 361 | const Widelands::Soldier* AttackBox::ListOfSoldiers::soldier_at(int32_t x, int32_t y) const { | ||
603 | 362 | if (x < 0 || y < 0 || soldiers_.empty()) { | ||
604 | 363 | return nullptr; | ||
605 | 364 | } | ||
606 | 365 | const int32_t col = x / kSoldierIconWidth; | ||
607 | 366 | const int32_t row = y / kSoldierIconHeight; | ||
608 | 367 | assert(col >= 0); | ||
609 | 368 | assert(row >= 0); | ||
610 | 369 | if ((restricted_row_number_ ? row : col) >= current_size_) { | ||
611 | 370 | return nullptr; | ||
612 | 371 | } | ||
613 | 372 | const int index = restricted_row_number_ ? current_size_ * col + row : current_size_ * row + col; | ||
614 | 373 | assert(index >= 0); | ||
615 | 374 | return static_cast<unsigned int>(index) < soldiers_.size() ? soldiers_[index] : nullptr; | ||
616 | 375 | } | ||
617 | 376 | |||
618 | 377 | void AttackBox::ListOfSoldiers::add(const Widelands::Soldier* s) { | ||
619 | 378 | soldiers_.push_back(s); | ||
620 | 379 | update_desired_size(); | ||
621 | 380 | } | ||
622 | 381 | |||
623 | 382 | void AttackBox::ListOfSoldiers::remove(const Widelands::Soldier* s) { | ||
624 | 383 | const auto it = std::find(soldiers_.begin(), soldiers_.end(), s); | ||
625 | 384 | assert(it != soldiers_.end()); | ||
626 | 385 | soldiers_.erase(it); | ||
627 | 386 | update_desired_size(); | ||
628 | 387 | } | ||
629 | 388 | |||
630 | 389 | void AttackBox::ListOfSoldiers::draw(RenderTarget& dst) { | ||
631 | 390 | const size_t nr_soldiers = soldiers_.size(); | ||
632 | 391 | int32_t column = 0; | ||
633 | 392 | int32_t row = 0; | ||
634 | 393 | for (uint32_t i = 0; i < nr_soldiers; ++i) { | ||
635 | 394 | Vector2i location(column * kSoldierIconWidth, row * kSoldierIconHeight); | ||
636 | 395 | soldiers_[i]->draw_info_icon(location, 1.0f, false, &dst); | ||
637 | 396 | if (restricted_row_number_) { | ||
638 | 397 | ++row; | ||
639 | 398 | if (row >= current_size_) { | ||
640 | 399 | row = 0; | ||
641 | 400 | ++column; | ||
642 | 401 | } | ||
643 | 402 | } else { | ||
644 | 403 | ++column; | ||
645 | 404 | if (column >= current_size_) { | ||
646 | 405 | column = 0; | ||
647 | 406 | ++row; | ||
648 | 407 | } | ||
649 | 408 | } | ||
650 | 409 | } | ||
651 | 410 | } | ||
652 | 411 | |||
653 | 176 | 412 | ||
654 | === modified file 'src/wui/attack_box.h' | |||
655 | --- src/wui/attack_box.h 2019-02-23 11:00:49 +0000 | |||
656 | +++ src/wui/attack_box.h 2019-05-11 10:49:50 +0000 | |||
657 | @@ -22,6 +22,8 @@ | |||
658 | 22 | 22 | ||
659 | 23 | #include <list> | 23 | #include <list> |
660 | 24 | #include <memory> | 24 | #include <memory> |
661 | 25 | #include <set> | ||
662 | 26 | #include <vector> | ||
663 | 25 | 27 | ||
664 | 26 | #include "graphic/font_handler.h" | 28 | #include "graphic/font_handler.h" |
665 | 27 | #include "graphic/text/font_set.h" | 29 | #include "graphic/text/font_set.h" |
666 | @@ -51,10 +53,18 @@ | |||
667 | 51 | 53 | ||
668 | 52 | void init(); | 54 | void init(); |
669 | 53 | 55 | ||
671 | 54 | uint32_t soldiers() const; | 56 | size_t count_soldiers() const; |
672 | 57 | std::vector<Widelands::Serial> soldiers() const; | ||
673 | 58 | void set_soldier_info_text(std::string text = "") { | ||
674 | 59 | current_soldier_stats_->set_text(text); | ||
675 | 60 | } | ||
676 | 61 | |||
677 | 62 | UI::Button* get_attack_button() const { | ||
678 | 63 | return attack_button_.get(); | ||
679 | 64 | } | ||
680 | 55 | 65 | ||
681 | 56 | private: | 66 | private: |
683 | 57 | uint32_t get_max_attackers(); | 67 | std::vector<Widelands::Soldier*> get_max_attackers(); |
684 | 58 | std::unique_ptr<UI::HorizontalSlider> add_slider(UI::Box& parent, | 68 | std::unique_ptr<UI::HorizontalSlider> add_slider(UI::Box& parent, |
685 | 59 | uint32_t width, | 69 | uint32_t width, |
686 | 60 | uint32_t height, | 70 | uint32_t height, |
687 | @@ -73,7 +83,7 @@ | |||
688 | 73 | const std::string& tooltip_text); | 83 | const std::string& tooltip_text); |
689 | 74 | 84 | ||
690 | 75 | void think() override; | 85 | void think() override; |
692 | 76 | void update_attack(); | 86 | void update_attack(bool); |
693 | 77 | void send_less_soldiers(); | 87 | void send_less_soldiers(); |
694 | 78 | void send_more_soldiers(); | 88 | void send_more_soldiers(); |
695 | 79 | 89 | ||
696 | @@ -88,6 +98,72 @@ | |||
697 | 88 | std::unique_ptr<UI::Button> less_soldiers_; | 98 | std::unique_ptr<UI::Button> less_soldiers_; |
698 | 89 | std::unique_ptr<UI::Button> more_soldiers_; | 99 | std::unique_ptr<UI::Button> more_soldiers_; |
699 | 90 | 100 | ||
700 | 101 | // A SoldierPanel is not applicable here as it's keyed to a building and thinks too much | ||
701 | 102 | struct ListOfSoldiers : public UI::Panel { | ||
702 | 103 | ListOfSoldiers(UI::Panel* const parent, | ||
703 | 104 | AttackBox* parent_box, | ||
704 | 105 | int32_t const x, | ||
705 | 106 | int32_t const y, | ||
706 | 107 | int const w, | ||
707 | 108 | int const h, | ||
708 | 109 | bool restrict_rows = false); | ||
709 | 110 | |||
710 | 111 | bool handle_mousepress(uint8_t btn, int32_t x, int32_t y) override; | ||
711 | 112 | void handle_mousein(bool) override; | ||
712 | 113 | bool handle_mousemove(uint8_t, int32_t, int32_t, int32_t, int32_t) override; | ||
713 | 114 | |||
714 | 115 | const Widelands::Soldier* soldier_at(int32_t x, int32_t y) const; | ||
715 | 116 | void add(const Widelands::Soldier*); | ||
716 | 117 | void remove(const Widelands::Soldier*); | ||
717 | 118 | bool contains(const Widelands::Soldier* soldier) const { | ||
718 | 119 | for (const auto& s : soldiers_) { | ||
719 | 120 | if (s == soldier) { | ||
720 | 121 | return true; | ||
721 | 122 | } | ||
722 | 123 | } | ||
723 | 124 | return false; | ||
724 | 125 | } | ||
725 | 126 | |||
726 | 127 | std::vector<const Widelands::Soldier*> get_soldiers() const { | ||
727 | 128 | return soldiers_; | ||
728 | 129 | } | ||
729 | 130 | const Widelands::Soldier* get_soldier() const { | ||
730 | 131 | return soldiers_.back(); | ||
731 | 132 | } | ||
732 | 133 | |||
733 | 134 | size_t count_soldiers() const { | ||
734 | 135 | return soldiers_.size(); | ||
735 | 136 | } | ||
736 | 137 | Widelands::Extent size() const; | ||
737 | 138 | bool row_number_restricted() const { | ||
738 | 139 | return restricted_row_number_; | ||
739 | 140 | } | ||
740 | 141 | void set_row_number_restricted(bool r) { | ||
741 | 142 | restricted_row_number_ = r; | ||
742 | 143 | } | ||
743 | 144 | |||
744 | 145 | void draw(RenderTarget& dst) override; | ||
745 | 146 | |||
746 | 147 | void set_complement(ListOfSoldiers* o) { | ||
747 | 148 | other_ = o; | ||
748 | 149 | } | ||
749 | 150 | |||
750 | 151 | private: | ||
751 | 152 | bool restricted_row_number_; | ||
752 | 153 | uint16_t current_size_; // Current number of rows or columns | ||
753 | 154 | std::vector<const Widelands::Soldier*> soldiers_; | ||
754 | 155 | |||
755 | 156 | ListOfSoldiers* other_; | ||
756 | 157 | AttackBox* attack_box_; | ||
757 | 158 | |||
758 | 159 | void update_desired_size() override; | ||
759 | 160 | }; | ||
760 | 161 | |||
761 | 162 | std::unique_ptr<ListOfSoldiers> attacking_soldiers_; | ||
762 | 163 | std::unique_ptr<ListOfSoldiers> remaining_soldiers_; | ||
763 | 164 | std::unique_ptr<UI::Textarea> current_soldier_stats_; | ||
764 | 165 | std::unique_ptr<UI::Button> attack_button_; | ||
765 | 166 | |||
766 | 91 | /// The last time the information in this Panel got updated | 167 | /// The last time the information in this Panel got updated |
767 | 92 | uint32_t lastupdate_; | 168 | uint32_t lastupdate_; |
768 | 93 | }; | 169 | }; |
769 | 94 | 170 | ||
770 | === modified file 'src/wui/fieldaction.cc' | |||
771 | --- src/wui/fieldaction.cc 2019-02-23 11:00:49 +0000 | |||
772 | +++ src/wui/fieldaction.cc 2019-05-11 10:49:50 +0000 | |||
773 | @@ -232,7 +232,6 @@ | |||
774 | 232 | static const char* const pic_geologist = "images/wui/fieldaction/menu_geologist.png"; | 232 | static const char* const pic_geologist = "images/wui/fieldaction/menu_geologist.png"; |
775 | 233 | 233 | ||
776 | 234 | static const char* const pic_tab_attack = "images/wui/fieldaction/menu_tab_attack.png"; | 234 | static const char* const pic_tab_attack = "images/wui/fieldaction/menu_tab_attack.png"; |
777 | 235 | static const char* const pic_attack = "images/wui/buildings/menu_attack.png"; | ||
778 | 236 | 235 | ||
779 | 237 | /* | 236 | /* |
780 | 238 | =============== | 237 | =============== |
781 | @@ -382,8 +381,9 @@ | |||
782 | 382 | attack_box_ = new AttackBox(&a_box, player_, &node_, 0, 0); | 381 | attack_box_ = new AttackBox(&a_box, player_, &node_, 0, 0); |
783 | 383 | a_box.add(attack_box_); | 382 | a_box.add(attack_box_); |
784 | 384 | 383 | ||
787 | 385 | set_fastclick_panel(&add_button( | 384 | UI::Button* attack_button = attack_box_->get_attack_button(); |
788 | 386 | &a_box, "attack", pic_attack, &FieldActionWindow::act_attack, _("Start attack"))); | 385 | attack_button->sigclicked.connect(boost::bind(&FieldActionWindow::act_attack, this)); |
789 | 386 | set_fastclick_panel(attack_button); | ||
790 | 387 | } | 387 | } |
791 | 388 | } | 388 | } |
792 | 389 | } | 389 | } |
793 | @@ -720,10 +720,9 @@ | |||
794 | 720 | assert(attack_box_); | 720 | assert(attack_box_); |
795 | 721 | upcast(Game, game, &ibase().egbase()); | 721 | upcast(Game, game, &ibase().egbase()); |
796 | 722 | if (upcast(Building, building, game->map().get_immovable(node_))) | 722 | if (upcast(Building, building, game->map().get_immovable(node_))) |
798 | 723 | if (attack_box_->soldiers() > 0) { | 723 | if (attack_box_->count_soldiers() > 0) { |
799 | 724 | upcast(InteractivePlayer const, iaplayer, &ibase()); | 724 | upcast(InteractivePlayer const, iaplayer, &ibase()); |
802 | 725 | game->send_player_enemyflagaction(building->base_flag(), iaplayer->player_number(), | 725 | game->send_player_enemyflagaction(building->base_flag(), iaplayer->player_number(), attack_box_->soldiers()); |
801 | 726 | attack_box_->soldiers() /* number of soldiers */); | ||
803 | 727 | } | 726 | } |
804 | 728 | reset_mouse_and_die(); | 727 | reset_mouse_and_die(); |
805 | 729 | } | 728 | } |
Continuous integration builds have changed state:
Travis build 4908. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 529279191. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ choose_ attack_ soldiers- 4689.
Appveyor build 4689. State: success. Details: https:/