diff --git a/public/dolumar/css/windows.css b/public/dolumar/css/windows.css index 4e602b4..3b086f4 100755 --- a/public/dolumar/css/windows.css +++ b/public/dolumar/css/windows.css @@ -805,6 +805,7 @@ h3 label { background: red; } + /* Server settings */ @@ -831,3 +832,212 @@ h3 label { margin-top: 16px; } + +/* + Defense formation: the attack picker's terrain slots, each with a front place and + (with enough regiments) a reserve place below it. Regiments are dragged or tapped in. +*/ +div.formation p.formation-help +{ + margin: 0 0 8px; +} + +div.formation p.formation-note +{ + margin: 6px 0 0; + font-size: 0.9em; +} + +div.formation div.formation-scroll +{ + overflow-x: auto; + overflow-y: hidden; + position: relative; + width: 100%; + -webkit-overflow-scrolling: touch; +} + +div.formation div.formation-field +{ + overflow: hidden; + margin: 0 auto; +} + +div.formation div.formation-slot +{ + float: left; + width: 75px; + height: 150px; + position: relative; +} + +div.formation span.formation-terrain +{ + position: absolute; + left: 2px; + right: 2px; + top: 3px; + font-size: 10px; + line-height: 14px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #40331f; + background: rgba(251, 243, 220, 0.85); + border-radius: 3px; +} + +div.formation div.formation-cell +{ + position: absolute; + left: 9px; + width: 55px; + height: 55px; + border: 1px dashed rgba(251, 243, 220, 0.9); + border-radius: 3px; +} + +div.formation div.formation-cell.front +{ + top: 22px; +} + +div.formation div.formation-slot.rows-1 div.formation-cell.front +{ + top: 52px; +} + +div.formation div.formation-cell.reserve +{ + top: 86px; +} + +div.formation div.formation-cell.filled +{ + border-color: transparent; +} + +div.formation span.formation-cell-label +{ + display: block; + margin-top: 19px; + font-size: 10px; + text-align: center; + color: #40331f; + background: rgba(251, 243, 220, 0.85); +} + +div.formation div.formation-cell.filled span.formation-cell-label +{ + display: none; +} + +div.formation div.formation-cell.reserve img.unit-selected +{ + opacity: 0.85; +} + +div.formation img.unit-selected +{ + position: absolute; + left: 1px; + top: 1px; + width: 50px; + height: 50px; + border: 1px solid black; +} + +div.formation div.clear-button +{ + position: absolute; + left: 2px; + right: 2px; + bottom: 3px; + text-align: center; + font-size: 10px; + line-height: 14px; +} + +div.formation div.clear-button span +{ + display: block; + color: #000097; + text-decoration: underline; + background: rgba(251, 243, 220, 0.9); + cursor: pointer; +} + +div.formation div.clear-button span:hover +{ + color: #b3261e; +} + +.tap-placing div.formation-cell +{ + cursor: pointer; + box-shadow: inset 0 0 0 2px rgba(240, 192, 64, 0.8); +} + +div.formation div.formation-cell.touch-over, +div.formation div.formation-cell.hover +{ + box-shadow: inset 0 0 0 3px #f0c040; +} + +div.formation div.formation-pool +{ + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +div.formation div.formation-unit +{ + width: 92px; + text-align: center; + font-size: 11px; + line-height: 14px; +} + +div.formation div.formation-unit img +{ + display: block; + width: 50px; + height: 50px; + margin: 0 auto 2px; + border: 1px solid black; + position: relative; + z-index: 1; +} + +div.formation div.formation-unit span +{ + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +div.formation div.formation-unit span.formation-unit-name +{ + font-weight: bold; +} + +/* Placed regiments stay in the list (drag them again to move them), dimmed. */ +div.formation div.formation-unit.placed img, +div.formation div.formation-unit.placed span +{ + opacity: 0.45; +} + +div.formation div.ui-btn-row +{ + margin-top: 10px; +} + +/* Tap-to-place marks the places, not the whole terrain column. */ +.tap-placing div.formation-slot +{ + box-shadow: none; +} diff --git a/public/dolumar/javascript/dolumar.js b/public/dolumar/javascript/dolumar.js index 0f35595..c3e9c42 100755 --- a/public/dolumar/javascript/dolumar.js +++ b/public/dolumar/javascript/dolumar.js @@ -499,9 +499,215 @@ Game.dolumar = Game.dolumar.enableTapToPlace (div, tapImages, tapSlots); }, + /* + Defense formation: the same terrain slots as the attack picker. A regiment is dragged + (or tapped, then a slot tapped) onto the front or reserve place of a slot. Each place + holds a hidden input slot__ that the window saves. A regiment stands in one + place at a time; dropping it on a taken place swaps the two. + */ + 'initFormationWindow' : function (oWindow) + { + var div = $(oWindow.div); + var form = div.select ('form.formation-form')[0]; + + if (!form) + { + return; + } + + // Content is replaced after saving: forget drop targets that left the page. + Droppables.drops.slice (0).each (function (drop) + { + if (!document.body.contains (drop.element)) + { + Droppables.remove (drop.element); + } + }); + + var scrolldiv = div.select ('div.formation-scroll')[0]; + var field = div.select ('div.formation-field')[0]; + var lng_clear = div.select ('span.formation-clear-text')[0].innerHTML; + + var units = {}; + var cells = div.select ('div.formation-cell'); + var tapImages = []; + var tapSlots = []; + + div.select ('div.formation-unit').each (function (box) + { + var img = box.select ('img')[0]; + var key = box.getAttribute ('data-unit'); + + img.id = oWindow.sDialogId + 'fimg' + key; + img.unitKey = key; + units[key] = { 'box' : box, 'img' : img }; + tapImages.push (img); + + img.dragable = new Draggable + ( + img.id, + { + 'revert' : true, + 'reverteffect' : function (element, top_offset, left_offset) + { + element.style.left = (parseInt (element.style.left) - left_offset) + 'px'; + element.style.top = (parseInt (element.style.top) - top_offset) + 'px'; + }, + // No fade effects: they leave an inline opacity over the "placed" dimming. + 'starteffect' : function (element) + { + element.style.opacity = 0.7; + }, + 'endeffect' : function (element) + { + element.style.opacity = ''; + } + } + ); + }); + + var inputOf = function (cell) + { + return cell.select ('input')[0]; + }; + + var cellOf = function (key) + { + for (var i = 0; i < cells.length; i ++) + { + if (inputOf (cells[i]).value == key) + { + return cells[i]; + } + } + + return null; + }; + + // Draw a place from its input: the regiment's image and a clear link, or empty. + var render = function (cell) + { + cell.select ('img.unit-selected, div.clear-button').each (function (el) + { + el.remove (); + }); + + var key = inputOf (cell).value; + var unit = units[key]; + + if (!unit) + { + cell.removeClassName ('filled'); + return; + } + + cell.addClassName ('filled'); + + var img = document.createElement ('img'); + img.src = unit.img.src; + img.title = unit.img.title; + img.alt = ''; + img.className = 'unit-selected'; + cell.appendChild (img); + + var clear = document.createElement ('div'); + clear.className = 'clear-button'; + clear.innerHTML = '' + lng_clear + ''; + clear.onclick = function () + { + inputOf (cell).value = '0'; + refresh (); + }; + cell.appendChild (clear); + }; + + var refresh = function () + { + // A reserve only counts behind a regiment in front of it: move it up. + div.select ('div.formation-slot').each (function (slot) + { + var places = slot.select ('div.formation-cell'); + + for (var i = 0; i < places.length - 1; i ++) + { + if (inputOf (places[i]).value == '0' && inputOf (places[i + 1]).value != '0') + { + inputOf (places[i]).value = inputOf (places[i + 1]).value; + inputOf (places[i + 1]).value = '0'; + } + } + }); + + cells.each (render); + + for (var key in units) + { + if (cellOf (key)) + { + units[key].box.addClassName ('placed'); + } + else + { + units[key].box.removeClassName ('placed'); + } + } + }; + + var place = function (key, cell) + { + var from = cellOf (key); + var current = inputOf (cell).value; + + if (from === cell) + { + return; + } + + // Swap: the regiment that stood here takes the place the new one came from. + if (from) + { + inputOf (from).value = current; + } + + inputOf (cell).value = key; + refresh (); + }; + + cells.each (function (cell, i) + { + cell.id = oWindow.sDialogId + 'fcell' + i; + + var dropOptions = + { + 'onDrop' : function (element, dropel) + { + place (element.unitKey, dropel); + return true; + }, + 'hoverclass' : 'hover', + 'scrollid' : scrolldiv + }; + + Droppables.add (cell.id, dropOptions); + tapSlots.push ({ 'slot' : cell, 'options' : dropOptions }); + }); + + // The slot row is as wide as its slots; on a narrow screen it scrolls. + var width = 0; + field.childElements ().each (function (el) + { + width += el.getWidth (); + }); + field.style.width = (width + 1) + 'px'; + + refresh (); + + Game.dolumar.enableTapToPlace (div, tapImages, tapSlots); + }, + /* Battle window - + TODO: Clean all dragables on destruction */ 'initBattleWindow' : function (oWindow) diff --git a/resources/language/de/battle.lng b/resources/language/de/battle.lng index a2f1762..86bfda6 100755 --- a/resources/language/de/battle.lng +++ b/resources/language/de/battle.lng @@ -97,6 +97,12 @@ row="Reihe" saved="Deine Formation wurde gespeichert." noSquads="Du hast noch keine Mannschaften." slot="Gebiet" +help="Ziehe eine Mannschaft auf ein Gebiet, oder tippe auf eine Mannschaft und dann auf ein Gebiet. Diese Formation wird verwendet, wenn dein Dorf angegriffen wird." +reserve="Reserve" +reserveHelp="Eine Reserve übernimmt das Gebiet, wenn die Mannschaft davor nicht zu Hause ist." +regiments="Deine Mannschaften" +unplaced="Mannschaften, die du nicht aufstellst, kämpfen auf einem zufälligen freien Gebiet." +clear="Leeren" [support] title="Sende Verstärkungstruppen nach @@target." diff --git a/resources/language/en/battle.lng b/resources/language/en/battle.lng index b513946..ef11d82 100755 --- a/resources/language/en/battle.lng +++ b/resources/language/en/battle.lng @@ -103,6 +103,12 @@ save = "Save formation" row = "Row" saved = "Your formation has been saved." noSquads = "You don't have any squads yet." +help = "Drag a regiment onto a slot, or tap a regiment and then a slot. This formation is used whenever your village is attacked." +reserve = "Reserve" +reserveHelp = "A reserve takes the slot when the regiment in front of it is away from home." +regiments = "Your regiments" +unplaced = "Regiments you leave out fight in a random free slot." +clear = "Clear" [support] title = "Send support units to @@target" diff --git a/resources/language/fr/battle.lng b/resources/language/fr/battle.lng index 3d03393..b18fa8a 100644 --- a/resources/language/fr/battle.lng +++ b/resources/language/fr/battle.lng @@ -30,6 +30,12 @@ row="Ligne" save="Sauver la formation" saved="Votre formation a été sauvegardée." slot="Position" +help="Faites glisser un peloton sur une position, ou touchez un peloton puis une position. Cette formation est utilisée chaque fois que votre village est attaqué." +reserve="Réserve" +reserveHelp="Une réserve prend la position quand le peloton devant elle n'est pas au village." +regiments="Vos pelotons" +unplaced="Les pelotons non placés combattent sur une position libre au hasard." +clear="Retirer" [overview] actions="Actions" diff --git a/resources/language/nl/battle.lng b/resources/language/nl/battle.lng index 0bf6271..13556dd 100755 --- a/resources/language/nl/battle.lng +++ b/resources/language/nl/battle.lng @@ -99,6 +99,12 @@ save="Formatie opslaan" row="Rij" saved="Je aanvalsformatie is opgeslagen." noSquads="Je hebt nog geen regimenten." +help="Sleep een regiment naar een plaats, of tik op een regiment en dan op een plaats. Deze formatie wordt gebruikt wanneer je dorp aangevallen wordt." +reserve="Reserve" +reserveHelp="Een reserve neemt de plaats in wanneer het regiment ervoor niet thuis is." +regiments="Jouw regimenten" +unplaced="Regimenten die je niet plaatst, vechten op een willekeurige vrije plaats." +clear="Wissen" [support] title="Stuur hulptroepen naar @@target" diff --git a/resources/language/pl/battle.lng b/resources/language/pl/battle.lng index a192a59..be4cba5 100755 --- a/resources/language/pl/battle.lng +++ b/resources/language/pl/battle.lng @@ -28,6 +28,12 @@ row="Rząd" save="Zapisz formację" saved="Twoja formacja została zapisana" slot="Otwór" +help="Przeciągnij oddział na pole albo dotknij oddziału, a potem pola. Ta formacja jest używana zawsze, gdy twoja wioska zostanie zaatakowana." +reserve="Rezerwa" +reserveHelp="Rezerwa zajmuje pole, gdy oddział przed nią jest poza domem." +regiments="Twoje oddziały" +unplaced="Oddziały, których nie ustawisz, walczą na losowym wolnym polu." +clear="Usuń" [overview] actions="Akcje" diff --git a/resources/templates/default/battle/formation.phpt b/resources/templates/default/battle/formation.phpt index b435d5d..312db85 100755 --- a/resources/templates/default/battle/formation.phpt +++ b/resources/templates/default/battle/formation.phpt @@ -2,36 +2,61 @@ -

getText ($message)?>

+

getText ($message)?>

-

getText ($message)?>

+

getText ($message)?>

- 0) { ?> -
-
- $slot) { ?> -
- - - - + 0) { ?> + getText ('reserve')?> - - - +
+ + + + +
 
+ + + + 1) { ?> +

getText ('reserveHelp')?>

+ + +

getText ('regiments')?>

+
+ +
+ + +
+
+ +

getText ('unplaced')?>

+ +
+ +
- -
+

getText ('noSquads')?>

diff --git a/src/Dolumar/Windows/Formation.php b/src/Dolumar/Windows/Formation.php index de516df..43d4fbd 100755 --- a/src/Dolumar/Windows/Formation.php +++ b/src/Dolumar/Windows/Formation.php @@ -51,8 +51,9 @@ public function setSettings () $this->setTitle ($text->get ('formation', 'menu', 'main')); } - $this->setSize (300, 300); + $this->setSize (460, 540); $this->setClassName ('formation'); + $this->setOnload ('Game.dolumar.initFormationWindow'); $this->setAllowOnlyOnce (); } @@ -79,32 +80,77 @@ public function getContent ($msg = null, $failed = false) // See how much rows that is $rows = $this->countRows ($squads, $slots); - $page->set ('rows', $rows); - $page->set ('slots', $slots); - + $text = Neuron_Core_Text::__getInstance (); + if (!empty ($msg)) { $page->set ('message', $msg); $page->set ('error', $failed); } - + + // The regiments that can be placed, and where each one stands now. + $placed = array (); + foreach ($squads as $v) { if ($v->getUnitsAmount () > 0) { - $page->addListValue - ( - 'squads', - array + foreach ($v->getUnits () as $unit) + { + $key = $v->getId () . '_' . $unit->getUnitId (); + + if ($unit->getDefaultSlot () > 0 && $unit->getSlotPriority () < $rows) + { + $placed[$unit->getDefaultSlot ()][$unit->getSlotPriority ()] = $key; + } + + $page->addListValue ( - 'sName' => Neuron_Core_Tools::output_varchar ($v->getName ()), - 'oUnits' => $v->getUnits (), - 'id' => $v->getId () - ) - ); + 'units', + array + ( + 'key' => $key, + 'sName' => Neuron_Core_Tools::output_varchar ($v->getName ()), + 'sUnit' => Neuron_Core_Tools::output_varchar ($unit->getName ()), + 'amount' => $unit->getAvailableAmount (), + 'image' => $unit->getImageUrl () + ) + ); + } } } - + + foreach ($slots as $slotId => $slot) + { + $cells = array (); + + for ($i = 0; $i < $rows; $i ++) + { + $cells[$i] = isset ($placed[$slotId][$i]) ? $placed[$slotId][$i] : '0'; + } + + // A reserve only counts behind a regiment in front of it (see processInput). + if ($rows > 1 && $cells[0] === '0' && $cells[1] !== '0') + { + $cells[0] = $cells[1]; + $cells[1] = '0'; + } + + $page->addListValue + ( + 'slots', + array + ( + 'id' => $slotId, + 'sName' => $slot->getName (), + 'sType' => $text->get ($slot->getName (), 'slots', 'battle'), + 'cells' => $cells + ) + ); + } + + $page->set ('rows', $rows); + return $page->parse ('battle/formation.phpt'); }