1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
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"
41 using namespace NLMISC
;
47 // ***************************************************************
48 CToolSelectMove::CToolSelectMove()
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)
79 uint aiCost
= (uint
) ls
.toInteger(-1);
81 if (!luaProj
.callMethodByNameNoThrow("getStaticObjectCost", 0, 1))
85 uint staticCost
= (uint
) ls
.toInteger(-1);
87 if (!getEditor().verifyRoomLeft(aiCost
, staticCost
))
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());
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);
137 newInst
->getDisplayerVisual()->setDisplayMode(CDisplayerVisual::DisplayModeFrozen
);
138 getEditor().getEntitySorter()->clipEntitiesByDist();
148 // ***************************************************************
149 void CToolSelectMove::beginAction(CInstance
&instance
)
151 _DeltaAnchor
.set(0, 0, 0);
152 //H_AUTO(R2_CToolSelectMove_beginAction)
154 CDisplayerVisual
*dv
= instance
.getDisplayerVisual();
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
182 switch(computeLandscapeRayIntersection(worldViewRay
, inter
))
197 case ISelectableObject::LocalSelectBox
:
198 case ISelectableObject::WorldSelectBox
:
200 _StartPos
= dv
->evalLinkPoint(false);
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
222 if (!isMouseOnWorldMap())
224 _DeltaX
= _MouseX
- (sint32
) (w
* projectedPos
.x
);
225 _DeltaY
= _MouseY
- (sint32
) (h
* projectedPos
.y
);
232 ///////////////////////
234 _PosRefX
= (sint32
) (w
* projectedPos
.x
);
235 _PosRefY
= (sint32
) (h
* projectedPos
.y
);
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 ///////////////////////
258 // see in advance if there will be room left to create a duplicate for this object
259 if (!checkAdditionnalRoomLeftFor(instance
))
262 getEditor().setCurrentTool(NULL
);
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();
280 dv
->setMoveInProgress(false);
283 std::string posInstanceId
= instance
.getPosInstanceId();
284 if (posInstanceId
.empty()) return;
285 getEditor().requestRollbackLocalNode(posInstanceId
, "");
289 // remove the ghost node
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();
306 dv
->setMoveInProgress(false);
309 cancelAction(instance
);
313 if (_Duplicating
&& _Moved
)
318 cancelAction(instance
);
321 if (!_GhostInstance
->getGhost())
323 cancelAction(instance
);
327 CLuaState
&ls
= getEditor().getLua();
328 CLuaObject luaProj
= _GhostInstance
->getLuaProjection();
329 if (!checkAdditionnalRoomLeftFor(*_GhostInstance
))
331 cancelAction(instance
);
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
);
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())
386 CUniquePtr
<CObject
> desc(CComLuaModule::getObjectFromLua(ls
.getStatePointer()));
387 _AutoGroup
.group(desc
.get(), _FinalPos
);
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();
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;
419 cancelAction(instance
);
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
, "");
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();
473 setMouseCursor(_Duplicating
? "curs_pan_dup.tga" : "curs_pan.tga");
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)
494 if (_Duplicating
&& !_GhostInstance
)
496 _GhostInstance
= createGhost(instance
);
500 getEditor().setCurrentTool(NULL
);
503 vd
= _GhostInstance
->getDisplayerVisual();
505 vdParent
= vd
->getParent();
510 // TODO : onMouseMove msg
515 CTool::CWorldViewRay worldViewRay
;
519 if (!isMouseOnWorldMap())
523 _FinalPos
= _LastValidPos
;
528 computeWorldViewRay(mx
- _DeltaX
, my
- _DeltaY
, worldViewRay
);
533 computeWorldViewRay(mx
, my
, worldViewRay
);
534 if (!worldViewRay
.OnMiniMap
)
536 _FinalPos
= _LastValidPos
;
542 CVector inter
; // intersection of view ray with landscape
544 switch(computeLandscapeRayIntersection(worldViewRay
, inter
))
547 // no collision, can't drop instance, so let it at its start position
548 _FinalPos
= _LastValidPos
;
552 //nlwarning("moving instance at pos %s", NLMISC::toString(inter).c_str());
554 _ValidPos
= isValid2DPos(inter
);
557 _LastValidPos
= _FinalPos
;
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())
567 _ValidPos
= true; // good pos to drop instance
580 _FinalPos
= _FinalPos
- _DeltaAnchor
;
581 // make final pos relative to parent
582 if (!_Duplicating
&& vd
->inheritPos() && instance
.getParent())
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())
599 if (vdParent
&& !vdParent
->isAccessible())
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())
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");
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
);
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
)
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))
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
);
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");