1 package cz.cuni.amis.pogamut.ut2004.bot.command;
2
3 import java.util.logging.Logger;
4
5 import javax.vecmath.Vector3d;
6
7 import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
8 import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
9 import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
10 import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
11 import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
12 import cz.cuni.amis.pogamut.base3d.worldview.object.Rotation;
13 import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
14 import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
15 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Configuration;
16 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.ContinuousMove;
17 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Dodge;
18 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Jump;
19 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Move;
20 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.TurnTo;
21 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Item;
22 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
23 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
24 import javax.vecmath.Matrix3d;
25
26 /**
27 * Class providing Pogamut2 UT2004 advanced locomotion commands for the bot -
28 * strafing, advanced turning, dodging...
29 *
30 * @author Michal 'Knight' Bida
31 */
32 public class AdvancedLocomotion extends SimpleLocomotion {
33
34 /**
35 * Self object holding information about our agent.
36 *
37 */
38 Self self = null;
39
40 /**
41 * {@link Self} listener. this is needed because self can be updated during
42 * the simulation and in multiPogamut we are stuck with the old version
43 */
44 private class SelfListener implements IWorldObjectEventListener<Self, WorldObjectUpdatedEvent<Self>> {
45
46 private IWorldView worldView;
47
48 /**
49 * Constructor. Registers itself on the given WorldView object.
50 *
51 * @param worldView WorldView object to listent to.
52 */
53 public SelfListener(IWorldView worldView) {
54 this.worldView = worldView;
55 worldView.addObjectListener(Self.class, WorldObjectUpdatedEvent.class, this);
56 }
57
58 @Override
59 public void notify(WorldObjectUpdatedEvent<Self> event) {
60 self = event.getObject();
61 }
62 }
63 /**
64 * {@link Self} listener
65 */
66 private SelfListener selfListener = null;
67 /**
68 * Used to set focus when strafing left and right, holds the distance of
69 * focus location.
70 */
71 private static final double FOCUS_DISTANCE = 3000;
72
73 /**
74 * Makes the bot to move through first location to second location (may be
75 * specified directly or some ILocated object may be supplied - carefull
76 * with objects traversability). Usage is when you want to have your bot to
77 * move really smooth. Where is the problem? If you would want to achive the
78 * same thing with 2 moveTo functions (first move to location1, when there
79 * move to location2), there may be a little lag - you have to check if you
80 * are already at first location and etc. This function can solve this
81 * problem as the check is done in UnrealScript.
82 *
83 * (issues GB MOVE command)
84 *
85 * @param firstLocation First location we will go through.
86 * @param secondLocation Second location we will go to (after reaching
87 * first).
88 *
89 * @see moveContinuous()
90 */
91 public void moveAlong(ILocated firstLocation, ILocated secondLocation) {
92 Move moveAlong = new Move();
93
94 moveAlong.setFirstLocation(firstLocation.getLocation());
95 moveAlong.setSecondLocation(secondLocation.getLocation());
96
97 agent.getAct().act(moveAlong);
98 }
99
100 /**
101 * This makes the bot to run straight ahead continuously. Will stop when
102 * other move command is issued - stopMovement, strafeTo, moveTo, moveAlong
103 * even turn commands will interrupt this.
104 *
105 * (issues GB CMOVE command)
106 *
107 * @see moveAlong(ILocated, ILocated)
108 *
109 */
110 public void moveContinuos() {
111 agent.getAct().act(new ContinuousMove());
112 }
113
114 /**
115 * Bot strafes right. The length of the strafe is specified by distance
116 * attribute (in UT units, 1 UT unit equals roughly 1 cm). The bot will be
117 * looking to object specified by the attribute focusId.
118 *
119 * @param distance - how far the bot strafes (in UT units, 1 UT unit equals
120 * roughly 1 cm).
121 * @param focusId - UnrealId of the object that should be the bot focus.
122 * @see strafeLeft(double,ILocated)
123 */
124 public void strafeRight(double distance, UnrealId focusId) {
125 if (self == null) {
126 self = agent.getWorldView().getSingle(Self.class);
127 }
128 if (self != null) {
129 Location startLoc = self.getLocation();
130 Location directionVector = self.getRotation().toLocation();
131 Location targetVec = directionVector.cross(new Location(0, 0, 1)).getNormalized().scale(-distance);
132
133 agent.getAct().act(
134 new Move().setFirstLocation(startLoc.add(targetVec)).setFocusTarget(focusId));
135 }
136 }
137
138 /**
139 * Bot strafes right. The length of the strafe is specified by distance
140 * attribute (in UT units, 1 UT unit equals roughly 1 cm). The bot will be
141 * looking to location specified by the attribute focusLocation.
142 *
143 * @param distance - how far the bot strafes (in UT units, 1 UT unit equals
144 * roughly 1 cm).
145 * @param focusLocation - location where the bot should look
146 * @see strafeLeft(double,ILocated)
147 */
148 public void strafeRight(double distance, ILocated focusLocation) {
149 if (self == null) {
150 self = agent.getWorldView().getSingle(Self.class);
151 }
152 if (self != null) {
153 Location startLoc = self.getLocation();
154 Location directionVector = self.getRotation().toLocation();
155 Location targetVec = directionVector.cross(new Location(0, 0, 1)).getNormalized().scale(-distance);
156
157 agent.getAct().act(
158 new Move().setFirstLocation(startLoc.add(targetVec)).setFocusLocation(focusLocation.getLocation()));
159 }
160 }
161
162 /**
163 * Bot strafes right. The length of the strafe is specified by distance
164 * attribute (in UT units, 1 UT unit equals roughly 1 cm). Note that this
165 * will reset the bot focus. The bot will be looking straight ahead (however
166 * if the strafe is really long - more than 500 UT units - it will be
167 * visible the bot is turning slightly performing the strafe).
168 *
169 * @param distance - how far the bot strafes (in UT units, 1 UT unit equals
170 * roughly 1 cm).
171 * @see strafeLeft(double)
172 */
173 public void strafeRight(double distance) {
174 if (self == null) {
175 self = agent.getWorldView().getSingle(Self.class);
176 }
177 if (self != null) {
178 Location startLoc = self.getLocation();
179 Location directionVector = self.getRotation().toLocation();
180 Location targetVec = directionVector.cross(new Location(0, 0, 1)).getNormalized().scale(-distance);
181
182 agent.getAct().act(
183 new Move().setFirstLocation(startLoc.add(targetVec)).setFocusLocation(
184 startLoc.add(directionVector.getNormalized().scale(
185 FOCUS_DISTANCE))));
186 }
187 }
188
189 /**
190 * Bot strafes left. The length of the strafe is specified by distance
191 * attribute (in UT units, 1 UT unit equals roughly 1 cm). The bot will be
192 * looking to object specified by the attribute focusId.
193 *
194 * @param distance - how far the bot strafes (in UT units, 1 UT unit equals
195 * roughly 1 cm).
196 * @param focusId - UnrealId of the object that should be the bot focus.
197 * @see strafeRight(double,ILocated)
198 */
199 public void strafeLeft(double distance, UnrealId focusId) {
200 if (self == null) {
201 self = agent.getWorldView().getSingle(Self.class);
202 }
203 if (self != null) {
204 Location startLoc = self.getLocation();
205 Location directionVector = self.getRotation().toLocation();
206 Location targetVec = directionVector.cross(new Location(0, 0, 1)).getNormalized().scale(distance);
207
208 agent.getAct().act(
209 new Move().setFirstLocation(startLoc.add(targetVec)).setFocusTarget(focusId));
210 }
211 }
212
213 /**
214 * Bot strafes left. The length of the strafe is specified by distance
215 * attribute (in UT units, 1 UT unit equals roughly 1 cm). The bot will be
216 * looking to location specified by the attribute focusLocation.
217 *
218 * @param distance - how far the bot strafes (in UT units, 1 UT unit equals
219 * roughly 1 cm).
220 * @param focusLocation - location where the bot should look
221 * @see strafeRight(double,ILocated)
222 */
223 public void strafeLeft(double distance, ILocated focusLocation) {
224 if (self == null) {
225 self = agent.getWorldView().getSingle(Self.class);
226 }
227 if (self != null) {
228 Location startLoc = self.getLocation();
229 Location directionVector = self.getRotation().toLocation();
230 Location targetVec = directionVector.cross(new Location(0, 0, 1)).getNormalized().scale(distance);
231
232 agent.getAct().act(
233 new Move().setFirstLocation(startLoc.add(targetVec)).setFocusLocation(focusLocation.getLocation()));
234 }
235 }
236
237 /**
238 * Bot strafes left. The length of the strafe is specified by distance
239 * attribute (in UT units, 1 UT unit equals roughly 1 cm). Note that this
240 * will reset the bot focus. The bot will be looking straight ahead (however
241 * if the strafe is really long - more than 500 UT units - it will be
242 * visible the bot is turning slightly performing the strafe).
243 *
244 * @param distance - how far the bot strafes (in UT units, 1 UT unit equals
245 * roughly 1 cm).
246 * @see strafeRight(double)
247 */
248 public void strafeLeft(double distance) {
249 if (self == null) {
250 self = agent.getWorldView().getSingle(Self.class);
251 }
252 if (self != null) {
253 Location startLoc = self.getLocation();
254 Location directionVector = self.getRotation().toLocation();
255 Location targetVec = directionVector.cross(new Location(0, 0, 1)).getNormalized().scale(distance);
256
257 agent.getAct().act(
258 new Move().setFirstLocation(startLoc.add(targetVec)).setFocusLocation(
259 startLoc.add(directionVector.getNormalized().scale(
260 FOCUS_DISTANCE))));
261 }
262 }
263
264 /**
265 * Makes the bot to move to location while looking at focusLocation. (issues
266 * GB STRAFE command)
267 *
268 * @param location Location we will strafe to.
269 * @param focusLocation Location we will look at while strafing.
270 *
271 * @see strafeTo(ILocated, UnrealId)
272 */
273 public void strafeTo(ILocated location, ILocated focusLocation) {
274 Move move = new Move().setFirstLocation(location.getLocation()).setFocusLocation(focusLocation.getLocation());
275 agent.getAct().act(move);
276 }
277
278 /**
279 * Makes the bot to move at location, while looking at focus object. Note
280 * that when you support focus object, the bot will update his focus (place
281 * he is looking at) according to focus object location (this will be
282 * provided by GB UnrealScript code). Usefull when you want to track some
283 * player position while moving somewhere else. (issues GB STRAFE command)
284 *
285 * @param location Location we will strafe to.
286 * @param focus Object with UrealId. We will look at this location while
287 * strafing. We will update our focus location according to the current
288 * position of this obejct in UT.
289 *
290 * @see strafeTo(ILocated, ILocated)
291 *
292 * @todo To check if supported object is also ILocated? see below
293 */
294 public void strafeTo(ILocated location, UnrealId focus) {
295 Move move = new Move().setFirstLocation(location.getLocation()).setFocusTarget(focus);
296 // TODO: To check if this object is also ILocated?
297 // How this could be done? We need to check if supported IWorldObject
298 // implements interface ILocated
299 /*
300 * ILocated tmpILocatedCheck; if (tmpILocatedCheck.getClass() ==
301 * focus.getClass().getInterfaces()[0]) {
302 *
303 * }
304 */
305 agent.getAct().act(move);
306 }
307
308 /**
309 * Makes the bot to double jump instantly (issues GB JUMP command) with
310 * default settings.
311 *
312 * @todo How to convince javadoc see to link to method in super class
313 *
314 * @see jump()
315 * @see dodge(Vector3d)
316 */
317 public void doubleJump() {
318 Jump jump = new Jump();
319 jump.setDoubleJump(true);
320
321 // TODO: [Michal Bida] remove when GB is fixed
322 jump.setForce((double) 680);
323
324 agent.getAct().act(jump);
325 }
326
327 /**
328 * Makes the bot to jump instantly (issues GB JUMP command) with custom
329 * settings. <p><p> See also {@link SimpleLocomotion#jump()}.
330 *
331 * @param doubleJump whether the bot should double jump
332 * @param secondJumpDelay If doubleJump, than after time specified here, the
333 * bot performs second jump of a double jump (if DoubleJump is true). Time
334 * is in seconds. GB2004 default is 0.5s.
335 * @param jumpZ than this is a force vector specifying how big the jump
336 * should be. Can't be set more than 2 * JumpZ = 680 for double jump.
337 *
338 * @see jump()
339 * @see dodge(Vector3d)
340 */
341 public void generalJump(boolean doubleJump, double secondJumpDelay, double jumpZ) {
342 Jump jump = new Jump();
343 jump.setDoubleJump(doubleJump);
344 if (doubleJump) {
345 jump.setDelay(secondJumpDelay);
346 }
347 jump.setForce(jumpZ);
348 agent.getAct().act(jump);
349 }
350
351 /**
352 * Makes the bot to double jump instantly (issues GB JUMP command) with
353 * custom settings. <p><p> See also {@link SimpleLocomotion#jump()}.
354 *
355 * @param secondJumpDelay After time specified here, the bot performs second
356 * jump of a double jump (if DoubleJump is true). Time is in seconds. GB2004
357 * default is 0.5s.
358 * @param jumpZ Force vector specifying how big the jump should be. Can't be
359 * set more than 2 * JumpZ = 680 for double jump.
360 *
361 * @see jump()
362 * @see dodge(Vector3d)
363 */
364 public void doubleJump(double secondJumpDelay, double jumpZ) {
365 Jump jump = new Jump();
366 jump.setDoubleJump(true);
367 jump.setDelay(secondJumpDelay);
368 jump.setForce(jumpZ);
369 agent.getAct().act(jump);
370 }
371
372 /**
373 * Makes the bot to dodge in the selected direction (this is in fact single
374 * jump that is executed to selected direction). Direction is absolute in
375 * this method. (issues GB DODGE command)
376 *
377 * @param direction Absolute vector (that will be normalized) that specifies
378 * direction of the jump.
379 * @param bDouble Wheter we want to perform double dodge.
380 * @see jump()
381 * @see doubleJump()
382 */
383 public void dodge(Location direction, boolean bDouble) {
384 direction = direction.getNormalized();
385 double alpha = Math.acos(self.getRotation().toLocation().getNormalized().dot(direction.getNormalized()));
386 double orientation = self.getRotation().toLocation().cross(new Location(0, 0, 1)).dot(direction);
387 if (orientation > 0) {
388 alpha = -alpha;
389 }
390
391 Matrix3d rot = Rotation.constructXYRot(alpha);
392 direction = new Location(1, 0, 0).mul(rot);
393
394 act.act(new Dodge().setDirection(direction).setDouble(bDouble));
395 }
396
397 /**
398 * Dodges from specified location to the left of the bot.
399 *
400 * @param inFrontOfTheBot Location to dodge from.
401 * @param bDouble Whether to perform double dodge.
402 */
403 public void dodgeLeft(ILocated inFrontOfTheBot, boolean bDouble) {
404 ILocated bot = self.getLocation();
405 Location direction = new Location(inFrontOfTheBot.getLocation().x - bot.getLocation().x, inFrontOfTheBot.getLocation().y - bot.getLocation().y, 0);
406 direction = direction.getNormalized();
407
408 double x = direction.getX();
409 double y = direction.getY();
410
411 direction = new Location(-y, x, 0);
412
413 double alpha = Math.acos(self.getRotation().toLocation().getNormalized().dot(direction.getNormalized()));
414 double orientation = self.getRotation().toLocation().cross(new Location(0, 0, 1)).dot(direction);
415 if (orientation > 0) {
416 alpha = -alpha;
417 }
418
419 Matrix3d rot = Rotation.constructXYRot(alpha);
420 direction = new Location(-1, 0, 0).mul(rot);
421
422 this.act.act(new Dodge().setDirection(direction).setDouble(bDouble));
423 }
424
425 /**
426 * Dodges from specified location to the right of the bot.
427 *
428 * @param inFrontOfTheBot Location to dodge from.
429 * @param bDouble Whether to perform double dodge.
430 */
431 public void dodgeRight(ILocated inFrontOfTheBot, boolean bDouble) {
432 ILocated bot = self.getLocation();
433 Location direction = new Location(inFrontOfTheBot.getLocation().x - bot.getLocation().x, inFrontOfTheBot.getLocation().y - bot.getLocation().y, 0);
434 direction = direction.getNormalized();
435
436 double x = direction.getX();
437 double y = direction.getY();
438
439 direction = new Location(-y, x, 0);
440
441 double alpha = Math.acos(self.getRotation().toLocation().getNormalized().dot(direction.getNormalized()));
442
443 double orientation = self.getRotation().toLocation().cross(new Location(0, 0, 1)).dot(direction);
444 if (orientation > 0) {
445 alpha = -alpha;
446 }
447
448 Matrix3d rot = Rotation.constructXYRot(alpha);
449 direction = new Location(1, 0, 0).mul(rot);
450
451 this.act.act(new Dodge().setDirection(direction).setDouble(bDouble));
452 }
453
454 /**
455 * Dodges from the specified location.
456 *
457 * @param inFrontOfTheBot Location to dodge from.
458 * @param bDouble Whether to perform double dodge.
459 */
460 public void dodgeBack(ILocated inFrontOfTheBot, boolean bDouble) {
461 ILocated bot = self.getLocation();
462 Location direction = new Location(bot.getLocation().x - inFrontOfTheBot.getLocation().x, bot.getLocation().y - inFrontOfTheBot.getLocation().y, 0);
463 direction = direction.getNormalized();
464
465 double alpha = Math.acos(self.getRotation().toLocation().getNormalized().dot(direction.getNormalized()));
466
467 double orientation = self.getRotation().toLocation().cross(new Location(0, 0, 1)).dot(direction);
468 if (orientation > 0) {
469 alpha = -alpha;
470 }
471
472 Matrix3d rot = Rotation.constructXYRot(alpha);
473 direction = new Location(1, 0, 0).mul(rot);
474
475 this.act.act(new Dodge().setDirection(direction).setDouble(bDouble));
476 }
477
478 /**
479 * Dodges towards the specified location.
480 *
481 * @param inFrontOfTheBot Location to dodge to.
482 * @param bDouble Whether to perform double dodge.
483 */
484 public void dodgeTo(ILocated inFrontOfTheBot, boolean bDouble) {
485 ILocated bot = self.getLocation();
486 Location direction = new Location(inFrontOfTheBot.getLocation().x - bot.getLocation().x, inFrontOfTheBot.getLocation().y - bot.getLocation().y, 0);
487 direction = direction.getNormalized();
488
489 double alpha = Math.acos(self.getRotation().toLocation().getNormalized().dot(direction.getNormalized()));
490
491 double orientation = self.getRotation().toLocation().cross(new Location(0, 0, 1)).dot(direction);
492 if (orientation > 0) {
493 alpha = -alpha;
494 }
495
496 Matrix3d rot = Rotation.constructXYRot(alpha);
497 direction = new Location(1, 0, 0).mul(rot);
498
499 this.act.act(new Dodge().setDirection(direction).setDouble(bDouble));
500 }
501
502 /**
503 * Makes the bot to dodge in the selected direction (this is in fact single
504 * jump that is executed to selected direction). Direction is relative to
505 * bot actual rotation! (issues GB DODGE command)
506 *
507 * @param direction Relative vector (that will be normalized) that specifies
508 * direction of the jump. Relative means that the bot current rotation will
509 * be added to the this vector. So to dodge ahead issue direction (1,0,0).
510 *
511 * @param bDouble Wheter we want to perform double dodge.
512 * @see jump()
513 * @see doubleJump()
514 */
515 public void dodgeRelative(Location direction, boolean bDouble) {
516 agent.getAct().act(new Dodge().setDirection(direction).setDouble(bDouble));
517 }
518
519 /**
520 * Sets the speed multiplier for the bot. By this number the bots default
521 * speed will be multiplied by. (issues GB CONF command)
522 *
523 * @param speedMultiplier Ranges from 0.1 to 2 (max may be set in ini in
524 * [RemoteBot] MaxSpeed)
525 *
526 * @see setRotationSpeed(Rotation)
527 */
528 public void setSpeed(double speedMultiplier) {
529 Configuration configure = new Configuration();
530 configure.setSpeedMultiplier(speedMultiplier);
531 agent.getAct().act(configure);
532 }
533
534 /**
535 * Sets the rotation speed (rotation rate) for the bot. Default rotation
536 * rate can be set in GameBots INI file in UT2004/System directory ( look
537 * for DefaultRotationRate attribute). Default rotation rate is now
538 * Pitch=3072, Yaw=60000, Roll=2048 (pitch = up/down, yaw = left/right, roll
539 * = equivalent of doing a cartwheel).
540 *
541 * (issues GB CONF command)
542 *
543 * @param newRotationRate Default is Pitch=3072, Yaw=60000, Roll=2048. To
544 * achieve best results we suggest to multiply the default setting.
545 *
546 * @see setSpeed(double)
547 */
548 public void setRotationSpeed(Rotation newRotationRate) {
549 Configuration configure = new Configuration();
550 configure.setRotationRate(newRotationRate);
551 agent.getAct().act(configure);
552 }
553
554 /**
555 * Constructor. Setups the command module based on given agent and logger.
556 *
557 * @param agent AbstractUT2004Bot we will send commands for
558 * @param log Logger to be used for logging runtime/debug info.
559 */
560 public AdvancedLocomotion(UT2004Bot agent, Logger log) {
561 super(agent, log);
562 this.selfListener = new SelfListener(agent.getWorldView()); //register self listener
563 }
564
565 @Override
566 public void jump() {
567 super.jump();
568 }
569
570 /**
571 * Makes the bot to jump instantly (issues GB JUMP command) with custom
572 * settings. <p><p> See also {@link SimpleLocomotion#jump()}.
573 *
574 * @param jumpZ Force vector specifying how big the jump should be. Can't be
575 * set more than JumpZ = 340 for single jump.
576 *
577 * @see jump()
578 * @see dodge(Vector3d)
579 */
580 public void jump(double jumpZ) {
581 Jump jump = new Jump();
582 jump.setForce(jumpZ);
583 agent.getAct().act(jump);
584 }
585
586 /**
587 * Makes the bot to jump (or double jump) instantly (issues GB JUMP command)
588 * with custom settings. <p><p> See also {@link SimpleLocomotion#jump()}.
589 *
590 * @param doubleJump whether to perform double jump
591 * @param secondJumpDelay After time specified here, the bot performs second
592 * jump of a double jump (if DoubleJump is true). Time is in seconds. GB2004
593 * default is 0.5s.
594 * @param jumpZ Force vector specifying how big the jump should be. Can't be
595 * set more than 2 * JumpZ = 680 for double jump.
596 *
597 * @see jump()
598 * @see dodge(Vector3d)
599 */
600 public void jump(boolean doubleJump, double secondJumpDelay, double jumpZ) {
601 Jump jump = new Jump();
602 jump.setDoubleJump(doubleJump);
603 jump.setDelay(secondJumpDelay);
604 jump.setForce(jumpZ);
605 agent.getAct().act(jump);
606 }
607
608 @Override
609 public void moveTo(ILocated location) {
610 super.moveTo(location);
611 }
612
613 @Override
614 public void setRun() {
615 super.setRun();
616 }
617
618 @Override
619 public void setWalk() {
620 super.setWalk();
621 }
622
623 @Override
624 public void stopMovement() {
625 super.stopMovement();
626 }
627
628 @Override
629 public void turnHorizontal(int amount) {
630 super.turnHorizontal(amount);
631 }
632
633 @Override
634 public void turnTo(ILocated location) {
635 super.turnTo(location);
636 }
637
638 @Override
639 public void turnTo(Player player) {
640 super.turnTo(player);
641 }
642
643 @Override
644 public void turnTo(Item item) {
645 super.turnTo(item);
646 }
647
648 @Override
649 public void turnVertical(int amount) {
650 super.turnVertical(amount);
651 }
652 }