eris 1.4.0
A WorldForge client library.
IGRouter.cpp
1#ifdef HAVE_CONFIG_H
2 #include "config.h"
3#endif
4
5#include "IGRouter.h"
6#include "Avatar.h"
7#include "Connection.h"
8#include "View.h"
9#include "Entity.h"
10#include "LogStream.h"
11#include "TypeInfo.h"
12#include "TypeBoundRedispatch.h"
13#include "TransferInfo.h"
14#include "TypeService.h"
15
16#include <Atlas/Objects/Operation.h>
17#include <Atlas/Objects/Entity.h>
18
19using namespace Atlas::Objects::Operation;
20using Atlas::Objects::Root;
21using Atlas::Objects::Entity::RootEntity;
22using Atlas::Objects::smart_dynamic_cast;
23using Atlas::Message::Element;
24
25namespace Eris {
26
27IGRouter::IGRouter(Avatar& av, View& view) :
28 m_avatar(av),
29 m_view(view)
30{
31 m_avatar.getConnection().registerRouterForTo(this, m_avatar.getEntityId());
32 m_actionType = m_avatar.getConnection().getTypeService().getTypeByName("action");
33}
34
35IGRouter::~IGRouter()
36{
37 m_avatar.getConnection().unregisterRouterForTo(this, m_avatar.getEntityId());
38}
39
40Router::RouterResult IGRouter::handleOperation(const RootOperation& op)
41{
42 if (!op->isDefaultSeconds()) {
43 // grab out world time
44 m_avatar.updateWorldTime(op->getSeconds());
45 }
46
47 const std::vector<Root>& args = op->getArgs();
48
49 if (op->getClassNo() == SIGHT_NO) {
50 if (args.empty()) {
51 warning() << "Avatar received sight with empty args";
52 return IGNORED;
53 }
54
55 for (const auto& arg : args) {
56 if (arg->instanceOf(ROOT_OPERATION_NO)) {
57 handleSightOp(op, smart_dynamic_cast<RootOperation>(arg));
58 } else {
59 // initial sight of entities
60 RootEntity gent = smart_dynamic_cast<RootEntity>(arg);
61 if (gent.isValid()) {
62 // View needs a bound TypeInfo for the entity
63 if (!gent->isDefaultId() && !gent->isDefaultParent()) {
64 TypeInfo* ty = m_avatar.getConnection().getTypeService().getTypeForAtlas(gent);
65 if (!ty->isBound()) {
66 auto opCopy = op.copy();
67 opCopy->setArgs1(arg);
68 new TypeBoundRedispatch(m_avatar.getConnection(), opCopy, ty);
69 } else {
70 m_view.sight(gent);
71 }
72 }
73 }
74 }
75 }
76
77 return HANDLED;
78
79
80 }
81
82 if (op->getClassNo() == APPEARANCE_NO) {
83 for (const auto& arg : args) {
84 double stamp = -1;
85 if (!arg->isDefaultStamp()) {
86 stamp = arg->getStamp();
87 }
88
89 if (!arg->isDefaultId()) {
90 m_view.appear(arg->getId(), stamp);
91 }
92 }
93
94 return HANDLED;
95 }
96
97 if (op->getClassNo() == DISAPPEARANCE_NO) {
98 for (const auto& arg : args) {
99 if (!arg->isDefaultId()) {
100 m_view.disappear(arg->getId());
101 }
102 }
103
104 return HANDLED;
105 }
106
107 if (op->getClassNo() == UNSEEN_NO)
108 {
109 if (args.empty()) {
110 warning() << "Avatar received unseen with empty args";
111 return IGNORED;
112 }
113 for (const auto& arg : args) {
114 if (!arg->isDefaultId()) {
115 m_view.unseen(arg->getId());
116 }
117 }
118 return HANDLED;
119 }
120
121 // logout
122 if (op->getClassNo() == LOGOUT_NO) {
123 debug() << "Avatar received forced logout from server";
124
125 if(args.size() >= 2) {
126 bool gotArgs = true;
127 // Teleport logout op. The second attribute is the payload for the teleport host data.
128 const Root & arg = args[1];
129 Element tp_host_attr;
130 Element tp_port_attr;
131 Element pkey_attr;
132 Element pentity_id_attr;
133 if(arg->copyAttr("teleport_host", tp_host_attr) != 0
134 || !tp_host_attr.isString()) {
135 debug() << "No teleport host specified. Doing normal logout."
136 << std::endl << std::flush;
137 gotArgs = false;
138 } else if (arg->copyAttr("teleport_port", tp_port_attr) != 0
139 || !tp_port_attr.isInt()) {
140 debug() << "No teleport port specified. Doing normal logout."
141 << std::endl << std::flush;
142 gotArgs = false;
143 } else if (arg->copyAttr("possess_key", pkey_attr) != 0
144 || !pkey_attr.isString()) {
145 debug() << "No possess key specified. Doing normal logout."
146 << std::endl << std::flush;
147 gotArgs = false;
148 } else if (arg->copyAttr("possess_entity_id", pentity_id_attr) != 0
149 || !pentity_id_attr.isString()) {
150 debug() << "No entity ID specified. Doing normal logout."
151 << std::endl << std::flush;
152 gotArgs = false;
153 }
154
155 // Extract argument data and request transfer only if we
156 // succeed in extracting them all
157 if (gotArgs) {
158 std::string teleport_host = tp_host_attr.String();
159 int teleport_port = static_cast<int>(tp_port_attr.Int());
160 std::string possess_key = pkey_attr.String();
161 std::string possess_entity_id = pentity_id_attr.String();
162 debug() << "Server transfer data: Host: " << teleport_host
163 << ", Port: " << teleport_port << ", "
164 << "Key: " << possess_key << ", "
165 << "ID: " << possess_entity_id << std::endl << std::flush;
166 // Now do a transfer request
167 TransferInfo transfer(teleport_host, teleport_port, possess_key
168 , possess_entity_id);
169 m_avatar.logoutRequested(transfer);
170 } else {
171 m_avatar.logoutRequested();
172 }
173
174 } else {
175 // Regular force logout op
176 m_avatar.logoutRequested();
177 }
178
179 return HANDLED;
180 }
181
182 return IGNORED;
183}
184
185Router::RouterResult IGRouter::handleSightOp(const RootOperation& sightOp, const RootOperation& op)
186{
187 const auto& args = op->getArgs();
188
189 // because a SET op can potentially (legally) update multiple entities,
190 // we decode it here, not in the entity router
191 if (op->getClassNo() == SET_NO) {
192 for (const auto& arg : args) {
193 if (!arg->isDefaultId()) {
194 auto ent = m_view.getEntity(arg->getId());
195 if (!ent) {
196 if (m_view.isPending(arg->getId())) {
197 /* no-op, we'll get the state later */
198 } else {
199 m_view.sendLookAt(arg->getId());
200 }
201
202 continue; // we don't have it, ignore
203 }
204
205 //If we get a SET op for an entity that's not visible, it means that the entity has moved
206 //within our field of vision without sending an Appear op first. We should treat this as a
207 //regular Appear op and issue a Look op back, to get more info.
208 if (!ent->isVisible()) {
209// float stamp = -1;
210// if (!arg->isDefaultStamp()) {
211// stamp = static_cast<float>(arg->getStamp());
212// }
213//
214// m_view.appear(arg->getId(), stamp);
215 m_view.getEntityFromServer(arg->getId());
216 } else {
217 ent->setFromRoot(arg, false);
218 }
219 }
220 }
221 return HANDLED;
222 }
223
224 if (!op->isDefaultParent()) {
225 // we have to handle generic 'actions' late, to avoid trapping interesting
226 // such as create or divide
227 TypeInfo* ty = m_avatar.getConnection().getTypeService().getTypeForAtlas(op);
228 if (!ty->isBound()) {
229 new TypeBoundRedispatch(m_avatar.getConnection(), sightOp, ty);
230 return HANDLED;
231 }
232
233 //For hits we want to check the "to" field rather than the "from" field. We're more interested in
234 //the entity that was hit than the one which did the hitting.
235 //Note that we'll let the op fall through, so that we later on handle the Hit action for the "from" entity.
236 if (op->getClassNo() == HIT_NO) {
237 if (!op->isDefaultTo()) {
238 Entity* ent = m_view.getEntity(op->getTo());
239 if (ent) {
240 ent->onHit(smart_dynamic_cast<Hit>(op), *ty);
241 }
242 } else {
243 warning() << "received hit with TO unset";
244 }
245 }
246
247
248 if (ty->isA(m_actionType)) {
249 if (op->isDefaultFrom()) {
250 warning() << "received op " << ty->getName() << " with FROM unset";
251 return HANDLED;
252 }
253
254 Entity* ent = m_view.getEntity(op->getFrom());
255 if (ent) {
256 ent->onAction(op, *ty);
257 }
258
259 return HANDLED;
260 }
261 }
262
263 return IGNORED;
264}
265
266} // of namespace Eris
Definition: Account.cpp:33