Add infos into target window
[ryzomcore.git] / ryzom / client / src / r2 / tool_select_move.cpp
blobe23d969ebfaee533fe243e02ae0af1f03f754cda
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdpch.h"
22 #include "editor.h"
23 #include "tool_select_move.h"
24 #include "entity_sorter.h"
26 #include "../interface_v3/interface_manager.h"
27 #include "../client_cfg.h"
28 #include "../entities.h"
29 #include "../global.h"
30 #include "displayer_visual.h"
31 #include "../sheet_manager.h"
33 #include "dmc/com_lua_module.h"
35 #include "nel/misc/matrix.h"
37 #ifdef DEBUG_NEW
38 #define new DEBUG_NEW
39 #endif
41 using namespace NLMISC;
44 namespace R2
47 // ***************************************************************
48 CToolSelectMove::CToolSelectMove()
50 _MouseX = -1;
51 _MouseY = -1;
52 _DeltaX = 0;
53 _DeltaY = 0;
54 _ValidPos = false;
55 _State = Idle;
56 _Moved = false;
57 _Duplicating = false;
58 _InitiallyAccessible = false;
59 _InitiallyValidShape = true;
60 _DeltaAnchor.set(0, 0, 0);
61 _StartPos.set(0, 0, 0);
62 _LastValidPos.set(0, 0, 0);
66 // ***************************************************************
67 bool CToolSelectMove::checkAdditionnalRoomLeftFor(CInstance &instance)
69 //H_AUTO(R2_CToolSelectMove_checkAdditionnalRoomLeftFor)
70 CLuaObject &luaProj = instance.getLuaProjection();
71 CLuaState &ls = getEditor().getLua();
72 CLuaStackRestorer lsr(&ls, 0);
73 // check ai & static cost : if they are too big, can't create the duplicate
74 if (!luaProj.callMethodByNameNoThrow("getAiCost", 0, 1)
75 || !ls.isInteger(-1))
77 return false;
79 uint aiCost = (uint) ls.toInteger(-1);
80 ls.pop();
81 if (!luaProj.callMethodByNameNoThrow("getStaticObjectCost", 0, 1))
83 return false;
85 uint staticCost = (uint) ls.toInteger(-1);
86 ls.pop();
87 if (!getEditor().verifyRoomLeft(aiCost, staticCost))
89 return false;
91 return true;
95 // ***************************************************************
96 CInstance *CToolSelectMove::createGhost(CInstance &instance)
98 //H_AUTO(R2_CToolSelectMove_createGhost)
99 CLuaState &ls = getEditor().getLua();
100 // copy then do a local paste
101 CLuaStackRestorer lsr(&ls, 0);
103 CLuaObject &luaProj = instance.getLuaProjection();
104 CLuaObject &classDef = instance.getClass();
105 if (luaProj.callMethodByNameNoThrow("copy", 0, 1))
107 // now we got a table that is an exact (canonical) copy of the original object, with the
108 // same instance ids..
109 // prepare for new insertion by renaming these instance id's (which 'newCopy' does)
110 if (classDef["newCopy"].callNoThrow(1, 1))
112 // now, insert the new copy as a ghost in the new scene
113 if (classDef["pasteGhost"].callNoThrow(1, 1))
116 CLuaObject ghost(ls); // pop the ghost from stack
117 CInstance *newInst = getEditor().getInstanceFromId(ghost["InstanceId"].toString());
118 if (newInst)
120 if (!newInst->getGhost())
122 nlwarning("When duplicating an object using the 'select/move' tool, temporary duplicate should be inserted \
123 as a ghost in the scene, removing object...");
124 getEditor().getDMC().requestEraseNode(newInst->getId(), "", -1);
126 // set the flag so that the cost of this object isn't taken in account in the displayed quotas
127 newInst->getLuaProjection()["User"].setValue("GhostDuplicate", true);
128 getEditor().setSelectedInstance(newInst);
129 newInst->getDisplayerVisual()->setDisplayFlag(CDisplayerVisual::FlagHideActivities, true);
130 nlwarning("CToolSelectMove: beginning duplicate with instance with id %s", newInst->getId().c_str());
131 // show in "frozen" state
133 /*CObjectNumber *numberValue = new CObjectNumber(2); // 2 = frozen state
134 getEditor().getDMC().requestSetNode(newInst->getId(), "DisplayMode", numberValue);
135 delete numberValue;
137 newInst->getDisplayerVisual()->setDisplayMode(CDisplayerVisual::DisplayModeFrozen);
138 getEditor().getEntitySorter()->clipEntitiesByDist();
139 return newInst;
145 return NULL;
148 // ***************************************************************
149 void CToolSelectMove::beginAction(CInstance &instance)
151 _DeltaAnchor.set(0, 0, 0);
152 //H_AUTO(R2_CToolSelectMove_beginAction)
153 _StartTime = T1;
154 CDisplayerVisual *dv = instance.getDisplayerVisual();
155 if(!dv)
157 cancel();
158 return;
160 _InitiallyAccessible = dv->isAccessible();
161 _InitiallyValidShape = true;
162 CDisplayerVisual *parentDV = dv->getParent();
163 if (parentDV && parentDV->isGroup())
165 _InitiallyAccessible = _InitiallyAccessible && parentDV->isAccessible();
166 _InitiallyValidShape = parentDV->isValidShape();
169 getMousePos(_MouseX, _MouseY);
170 // must work with eval link point because world pos may be realy far from link pos (for regions)
171 ISelectableObject::TSelectionType selectionType = dv->getSelectionType();
172 switch(selectionType)
174 case ISelectableObject::GroundProjected:
176 // because displayed instance is projected on scene (like roads, regions ...)
177 // anchor point is scene / mouse ray intersection point
178 CTool::CWorldViewRay worldViewRay;
179 computeWorldViewRay(_MouseX, _MouseY, worldViewRay);
180 CVector inter; // intersection of view ray with landscape
181 _ValidPos = false;
182 switch(computeLandscapeRayIntersection(worldViewRay, inter))
184 case NoIntersection:
185 return;
186 break;
187 case ValidPacsPos:
188 case InvalidPacsPos:
189 _StartPos = inter;
190 break;
191 default:
192 nlassert(0);
193 break;
196 break;
197 case ISelectableObject::LocalSelectBox:
198 case ISelectableObject::WorldSelectBox:
199 dv->snapToGround();
200 _StartPos = dv->evalLinkPoint(false);
201 break;
202 default:
203 nlassert(0);
204 break;
206 _DeltaAnchor = _StartPos - dv->getWorldPos();
207 // nico : tmp fix to have correct z until luaSnapToGround is fixed
208 if (instance.getEntity())
210 instance.getEntity()->snapToGround();
211 _StartPos.z = instance.getEntity()->pos().z;
213 //nlwarning("starting to move instance at pos %s", NLMISC::toString(_StartPos.asVector()).c_str());
214 const NL3D::CFrustum &fru = MainCam.getFrustum();
215 CVector posInCam = MainCam.getMatrix().inverted() * _StartPos;
216 CVector projectedPos = fru.projectZ(posInCam);
217 /** Compute delta in screen space between instance position and the click position
218 * We maintain that delta when the instance moves
220 uint32 w, h;
221 getScreenSize(w, h);
222 if (!isMouseOnWorldMap())
224 _DeltaX = _MouseX - (sint32) (w * projectedPos.x);
225 _DeltaY = _MouseY - (sint32) (h * projectedPos.y);
227 else
229 _DeltaX = 0;
230 _DeltaY = 0;
232 ///////////////////////
233 // TMP TMP TMP
234 _PosRefX = (sint32) (w * projectedPos.x);
235 _PosRefY = (sint32) (h * projectedPos.y);
239 CVector debugPos;
240 CVector rayOrigin;
241 CVector rayDir;
242 computeWorldViewRay(_MouseX - _DeltaX, _MouseY - _DeltaY, rayOrigin, rayDir);
243 computeLandscapeRayIntersection(rayOrigin, rayOrigin + 1000.f * rayDir, debugPos);
245 //nlwarning("ref point = %s", NLMISC::toString(debugPos).c_str());
247 _RefX = _MouseX - _DeltaX;
248 _RefY = _MouseY - _DeltaY;
250 _MouseRefX = _MouseX;
251 _MouseRefY = _MouseY;
252 ///////////////////////
253 _ValidPos = false;
254 _Moved = false;
256 if (isShiftDown())
258 // see in advance if there will be room left to create a duplicate for this object
259 if (!checkAdditionnalRoomLeftFor(instance))
261 _State = Idle;
262 getEditor().setCurrentTool(NULL);
263 return;
265 _Duplicating = true;
266 _ValidPos = true; // valid pos for first frame
267 _FinalPos = _StartPos;
268 _GhostInstance = NULL;
270 _LastValidPos = _StartPos;
271 dv->setMoveInProgress(true);
274 // ***************************************************************
275 void CToolSelectMove::cancelAction(CInstance &instance)
277 //H_AUTO(R2_CToolSelectMove_cancelAction)
278 CDisplayerVisual *dv = instance.getDisplayerVisual();
279 nlassert(dv);
280 dv->setMoveInProgress(false);
281 if (_Moved)
283 std::string posInstanceId = instance.getPosInstanceId();
284 if (posInstanceId.empty()) return;
285 getEditor().requestRollbackLocalNode(posInstanceId, "");
287 if (_Duplicating)
289 // remove the ghost node
290 if (_GhostInstance)
292 nlassert(_GhostInstance->getGhost()); // should have been inserted as a ghost in the scene
294 getEditor().getDMC().requestEraseNode(_GhostInstance->getId(), "", -1);
297 _Duplicating = false;
300 // ***************************************************************
301 void CToolSelectMove::commitAction(CInstance &instance)
303 //H_AUTO(R2_CToolSelectMove_commitAction)
304 CDisplayerVisual *dv = instance.getDisplayerVisual();
305 nlassert(dv);
306 dv->setMoveInProgress(false);
307 if (!_ValidPos)
309 cancelAction(instance);
311 else
313 if (_Duplicating && _Moved)
316 if (!_GhostInstance)
318 cancelAction(instance);
319 return;
321 if (!_GhostInstance->getGhost())
323 cancelAction(instance);
324 return;
327 CLuaState &ls = getEditor().getLua();
328 CLuaObject luaProj = _GhostInstance->getLuaProjection();
329 if (!checkAdditionnalRoomLeftFor(*_GhostInstance))
331 cancelAction(instance);
332 return;
336 CLuaStackRestorer lsr(&ls, ls.getTop());
337 // duplicate the ghost, and insert in the scene for real
338 CLuaObject classDef = instance.getClass();
339 std::string oldName = luaProj["Name"].toString(); // keep name, because the newCopy call
340 // will generate a newone, causing indices to increase 2 by 2
342 // restore default value for display mode
343 //getEditor().getDMC().requestEraseNode(_GhostInstance->getId(), "DisplayMode", -1);
344 if (_GhostInstance->getDisplayerVisual())
346 _GhostInstance->getDisplayerVisual()->setDisplayMode(CDisplayerVisual::DisplayModeVisible);
349 if (_Moved)
351 if (luaProj.callMethodByNameNoThrow("copy", 0, 1))
354 std::string posInstanceId = instance.getPosInstanceId();
355 if (!posInstanceId.empty())
357 getEditor().requestRollbackLocalNode(posInstanceId, ""); // Locally modified coords have
358 // (of a local object ...)
359 // have been read during the copy,
360 // get rid of them...
363 getDMC().newAction(CI18N::get("uiR2EDCopyAction") + _GhostInstance->getDisplayName());
364 getEditor().getDMC().requestEraseNode(_GhostInstance->getId(), "", -1);
365 // now, instanciate cannonical copy
366 if (classDef["newCopy"].callNoThrow(1, 1))
368 // set real pos for the copy & the name
369 CLuaObject newCopy(ls);
370 CLuaObject pos = newCopy["Position"];
373 newCopy.setValue("Name", oldName);
374 pos.setValue("x", _FinalPos.x);
375 pos.setValue("y", _FinalPos.y);
376 pos.setValue("z", _FinalPos.z);
378 catch(const ELuaNotATable &)
380 nlwarning("Error while setting position of copied object");
382 // ... and paste for real
383 if (_AutoGroup.getGroupingCandidate())
385 newCopy.push();
386 CUniquePtr<CObject> desc(CComLuaModule::getObjectFromLua(ls.getStatePointer()));
387 _AutoGroup.group(desc.get(), _FinalPos);
389 else
391 newCopy.push();
392 ls.push(false); // second parameter is for "not a new place"
393 ls.push(instance.getId()); // last param give the original instance id of the object being copied
394 classDef["paste"].callNoThrow(3, 1);
395 getEditor().getDMC().getActionHistoric().endAction();
396 getEditor().getDMC().flushActions();
400 else
402 std::string posInstanceId = instance.getPosInstanceId();
403 if (!posInstanceId.empty())
405 getEditor().requestRollbackLocalNode(posInstanceId, ""); // Locally modified coords
406 // (of a local object ...)
407 // have been read during the copy,
408 // get rid of them...
412 _Duplicating = false;
414 else
415 if (_Moved)
417 if (!_ValidPos)
419 cancelAction(instance);
421 else
423 string instanceName = instance.getDisplayName().toUtf8();
424 if(instanceName == CI18N::get("uiR2EDNoName"))
425 instanceName = instance.getClassName();
427 //getDMC().newAction(CI18N::get("uiR2EDMoveAction") + instance.getDisplayName());
428 getDMC().newAction(CI18N::get("uiR2EDMoveAction") + instanceName);
429 std::string posInstanceId = instance.getPosInstanceId();
430 if (posInstanceId.empty()) return;
431 std::string instanceId = instance.getObjectTable()->getAttr("InstanceId")->toString();
432 R2::getEditor().getLua().push(instanceId);
433 R2::getEditor().callEnvFunc( "checkLeaderDistAndUngroup", 1, 0);
434 getEditor().requestCommitLocalNode(posInstanceId, "");
437 else
439 cancelAction(instance);
444 // ***************************************************************
445 const char *CToolSelectMove::getCursorForPossibleAction() const
447 //H_AUTO(R2_CToolSelectMove_getCursorForPossibleAction)
448 return isShiftDown() ? "curs_can_pan_dup.tga" : "curs_can_pan.tga";
451 // ***************************************************************
452 const char *CToolSelectMove::getDefaultCursor() const
454 //H_AUTO(R2_CToolSelectMove_getDefaultCursor)
455 if (isMouseOnUI() && !isMouseOnWorldMap()) return DEFAULT_CURSOR;
456 return isShiftDown() ? "curs_dup.tga" : DEFAULT_CURSOR;
459 // ***************************************************************
460 const char *CToolSelectMove::getPickCursor() const
462 //H_AUTO(R2_CToolSelectMove_getPickCursor)
463 return isShiftDown() ? "curs_pick_dup.tga" : "curs_pick.tga";
467 // ***************************************************************
468 void CToolSelectMove::updateAction(CInstance &instance)
470 //H_AUTO(R2_CToolSelectMove_updateAction)
471 CDisplayerVisual *vd = _GhostInstance ? _GhostInstance->getDisplayerVisual() : instance.getDisplayerVisual();
472 if(!vd) return;
473 setMouseCursor(_Duplicating ? "curs_pan_dup.tga" : "curs_pan.tga");
474 sint32 mx, my;
475 getMousePos(mx, my);
476 sint32 autoPanDx, autoPanDy;
477 handleWorldMapAutoPan(autoPanDx, autoPanDy);
478 CDisplayerVisual *vdParent = vd->getParent();
479 if (mx != _MouseX || my != _MouseY || autoPanDx || autoPanDy)
481 if (!autoPanDx && !autoPanDy)
483 const sint32 moveThreshold = 2;
484 const sint32 timeThreshold = 300; // if some time elapsed, then do the move
485 if (!_Moved && abs(mx - _MouseRefX) < moveThreshold && abs(my - _MouseRefY) < moveThreshold &&
486 (T1 - _StartTime) < timeThreshold
489 // discard small displacement when clicking (move is unwanted most of the time)
490 return;
494 if (_Duplicating && !_GhostInstance)
496 _GhostInstance = createGhost(instance);
497 if (!_GhostInstance)
499 _State = Idle;
500 getEditor().setCurrentTool(NULL);
501 return;
503 vd = _GhostInstance->getDisplayerVisual();
504 if (!vd) return;
505 vdParent = vd->getParent();
508 _Moved = true;
509 // mouse has moved
510 // TODO : onMouseMove msg
511 // update mouse pos
512 _MouseX = mx;
513 _MouseY = my;
515 CTool::CWorldViewRay worldViewRay;
517 _ValidPos = true;
519 if (!isMouseOnWorldMap())
521 if (isMouseOnUI())
523 _FinalPos = _LastValidPos;
524 _ValidPos = false;
526 else
528 computeWorldViewRay(mx - _DeltaX, my - _DeltaY, worldViewRay);
531 else
533 computeWorldViewRay(mx, my, worldViewRay);
534 if (!worldViewRay.OnMiniMap)
536 _FinalPos = _LastValidPos;
537 _ValidPos = false;
540 if (_ValidPos)
542 CVector inter; // intersection of view ray with landscape
543 _ValidPos = false;
544 switch(computeLandscapeRayIntersection(worldViewRay, inter))
546 case NoIntersection:
547 // no collision, can't drop instance, so let it at its start position
548 _FinalPos = _LastValidPos;
549 _ValidPos = false;
550 break;
551 case ValidPacsPos:
552 //nlwarning("moving instance at pos %s", NLMISC::toString(inter).c_str());
553 _FinalPos = inter;
554 _ValidPos = isValid2DPos(inter);
555 if (_ValidPos)
557 _LastValidPos = _FinalPos;
560 break;
561 case InvalidPacsPos:
562 // If the object moved is a region or a group, invalid pacs pos
563 // may still be acceptable as long as an intersection was found
564 if (vd->isInvalidPacsPosAcceptable())
566 _FinalPos = inter;
567 _ValidPos = true; // good pos to drop instance
569 else
571 _FinalPos = inter;
572 _ValidPos = false;
574 break;
575 default:
576 nlassert(0);
577 break;
580 _FinalPos = _FinalPos - _DeltaAnchor;
581 // make final pos relative to parent
582 if (!_Duplicating && vd->inheritPos() && instance.getParent())
584 if (vdParent)
586 _FinalPos = _FinalPos - vdParent->getWorldPos();
590 setInstancePos(_FinalPos, instance);
592 if (_InitiallyAccessible)
594 // NB : do not do the test if initially inaccessible to allow user to go to an 'accessible' state more easily
595 if (!vd->isAccessible())
597 _ValidPos = false;
599 if (vdParent && !vdParent->isAccessible())
601 _ValidPos = false;
605 if (_InitiallyValidShape)
607 // is started from an invalid shape, allow to use intermediate invalid shapes to correct it
608 if (vdParent && vdParent->isGroup() && !vdParent->isValidShape())
610 _ValidPos = false;
614 if (_Moved && _ValidPos)
616 std::string instanceId = instance.getObjectTable()->getAttr("InstanceId")->toString();
617 R2::getEditor().getLua().push(instanceId);
618 R2::getEditor().callEnvFunc( "checkGroupDistance", 1, 1);
619 if (!R2::getEditor().getLua().isBoolean(-1))
621 nlassert(0 && "checkGroupDistance return wrong type");
623 bool positionOk = R2::getEditor().getLua().toBoolean(-1);
624 R2::getEditor().getLua().pop();
625 _ValidPos = positionOk;
630 if (!_ValidPos && _Moved)
632 setMouseCursor("curs_stop.tga");
634 // TMP TMP
637 uint32 w, h;
638 getScreenSize(w, h);
639 Driver->setMatrixMode3D(MainCam);
640 Driver->setModelMatrix(CMatrix::Identity);
641 drawBox(_StartPos - CVector(0.02f, 0.02f, 0.02f), _StartPos + CVector(0.02f, 0.02f, 0.02f), CRGBA::Magenta);
642 Driver->setMatrixMode2D11();
643 Driver->drawLine((_RefX - 5) / (float) w, _RefY / (float) h, (_RefX + 5) / (float) w, _RefY / (float) h, CRGBA::Red);
644 Driver->drawLine(_RefX / (float) w, (_RefY - 5) / (float) h, _RefX / (float) w, (_RefY + 5) / (float) h, CRGBA::Red);
646 Driver->drawLine((_PosRefX - 5) / (float) w, _PosRefY / (float) h, (_PosRefX + 5) / (float) w, _PosRefY / (float) h, CRGBA::Green);
647 Driver->drawLine(_PosRefX / (float) w, (_PosRefY - 5) / (float) h, _PosRefX / (float) w, (_PosRefY + 5) / (float) h, CRGBA::Green);
649 Driver->drawLine((_MouseRefX - 5) / (float) w, _MouseRefY / (float) h, (_MouseRefX + 5) / (float) w, _MouseRefY / (float) h, CRGBA::Yellow);
650 Driver->drawLine(_MouseRefX / (float) w, (_MouseRefY - 5) / (float) h, _MouseRefX / (float) w, (_MouseRefY + 5) / (float) h, CRGBA::Yellow);
656 // ***************************************************************
657 void CToolSelectMove::setInstancePos(const NLMISC::CVectorD &pos, CInstance &instance)
659 //H_AUTO(R2_CToolSelectMove_setInstancePos)
660 std::string posInstanceId = instance.getPosInstanceId();
661 if (posInstanceId.empty()) return;
662 CObject *newPos = buildVector(pos, posInstanceId);
663 getEditor().requestSetLocalNode(_GhostInstance ? _GhostInstance->getId() : instance.getId(), "Position", newPos);
664 delete newPos;
667 // ***************************************************************
668 bool CToolSelectMove::isActionPossibleOn(const CInstance &instance) const
670 //H_AUTO(R2_CToolSelectMove_isActionPossibleOn)
671 CInstance &mutableInstance = const_cast<CInstance &>(instance);
672 CDisplayerVisual *dv = mutableInstance.getDisplayerVisual();
673 if (dv && dv->getActualDisplayMode() != CDisplayerVisual::DisplayModeVisible)
675 return false;
677 if (isShiftDown())
679 CLuaStackRestorer lsr(&getEditor().getLua(), 0);
680 mutableInstance.getLuaProjection().callMethodByNameNoThrow("isCopyable", 0, 1); // isCopyable should be 'const', so no worries here ...
681 if (getEditor().getLua().toBoolean(1))
683 return true;
685 return false;
687 return true;
690 // ***************************************************************
691 void CToolSelectMove::onActivate()
693 //H_AUTO(R2_CToolSelectMove_onActivate)
694 setContextHelp(CI18N::get("uiR2EDToolSelectMove"));
697 // ***************************************************************
698 void CToolSelectMove::updateBeforeRender()
700 //H_AUTO(R2_CToolSelectMove_updateBeforeRender)
701 if (_Duplicating && _GhostInstance)
703 _AutoGroup.update(_FinalPos, _GhostInstance->getPaletteId(), _ValidPos && !isCtrlDown());
704 _GhostInstance->getDisplayerVisual()->setDisplayFlag(CDisplayerVisual::FlagHideActivities, _AutoGroup.getGroupingCandidate() != NULL);
706 else
708 if (!_Duplicating && _GhostInstance) _GhostInstance->getDisplayerVisual()->setDisplayFlag(CDisplayerVisual::FlagHideActivities, false);
712 /////////////////////
713 // ACTION HANDLERS //
714 /////////////////////
718 * Make the select/move tool current
720 class CAHSelectMove : public IActionHandler
722 virtual void execute(CCtrlBase * /* pCaller */, const std::string &/* sParams */)
724 getEditor().setCurrentTool(new CToolSelectMove);
727 REGISTER_ACTION_HANDLER(CAHSelectMove, "r2ed_select_move");
731 } // R2