1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "outfit.h"
24#include "game.h"
25#include "spritemanager.h"
26
27#include <framework/graphics/painter.h>
28#include <framework/graphics/drawcache.h>
29#include <framework/graphics/drawqueue.h>
30#include <framework/graphics/atlas.h>
31#include <framework/graphics/texturemanager.h>
32#include <framework/graphics/image.h>
33#include <framework/graphics/framebuffermanager.h>
34#include <framework/graphics/shadermanager.h>
35
36Outfit::Outfit()
37{
38 m_category = ThingCategoryCreature;
39 m_id = 128;
40 m_auxId = 0;
41 resetClothes();
42}
43
44void Outfit::draw(Point dest, Otc::Direction direction, uint walkAnimationPhase, bool animate, LightView* lightView, bool ui)
45{
46 // direction correction
47 if (m_category != ThingCategoryCreature)
48 direction = Otc::North;
49 else if (direction == Otc::NorthEast || direction == Otc::SouthEast)
50 direction = Otc::East;
51 else if (direction == Otc::NorthWest || direction == Otc::SouthWest)
52 direction = Otc::West;
53
54 Point wingDest = dest;
55 if (g_game.getFeature(Otc::GameWingOffset) && m_wings)
56 dest -= Point(6, 6) * g_sprites.getOffsetFactor();
57
58 auto type = g_things.rawGetThingType(m_category == ThingCategoryCreature ? m_id : m_auxId, m_category);
59 if (!type) return;
60
61 if (g_game.getFeature(Otc::GameCenteredOutfits)) {
62 dest.x += ((type->getWidth() - 1) * (g_sprites.spriteSize() / 2));
63 }
64
65 int animationPhase = walkAnimationPhase;
66
67 auto wingBounce = [&] {
68 int maxoffset = 4;
69 uint floatingTicks = 8;
70 uint tick = (g_clock.millis() % (1000)) / (1000 / floatingTicks);
71 int offset = 0;
72 if (walkAnimationPhase > 0) {
73 auto idleAnimator = type->getIdleAnimator();
74 if (idleAnimator) {
75 animationPhase = idleAnimator->getPhase();
76 } else {
77 animationPhase = 0;
78 }
79 }
80 offset = tick <= floatingTicks / 2 ? tick * (maxoffset / (floatingTicks / 2)) : (2 * maxoffset) - tick * (maxoffset / (floatingTicks / 2));
81 dest -= Point(offset, offset) * g_sprites.getOffsetFactor();
82 wingDest -= Point(offset, offset) * g_sprites.getOffsetFactor();
83 };
84
85 if (animate && m_category == ThingCategoryCreature) {
86 auto idleAnimator = type->getIdleAnimator();
87 if (idleAnimator && !ui) {
88 if (walkAnimationPhase > 0) {
89 animationPhase += idleAnimator->getAnimationPhases() - 1;
90 } else {
91 animationPhase = idleAnimator->getPhase();
92 }
93 } else if (type->isAnimateAlways() || ui) {
94 int phases = type->getAnimator() ? type->getAnimator()->getAnimationPhases() : type->getAnimationPhases();
95 if (ui && phases < 4) {
96 phases = 2; // old protocols with 2 frames walk animation
97 }
98 int ticksPerFrame = !g_game.getFeature(Otc::GameEnhancedAnimations) ? 333 : (1000 / phases);
99 animationPhase = (g_clock.millis() % (ticksPerFrame * phases)) / ticksPerFrame;
100 if (idleAnimator && ui) {
101 animationPhase += idleAnimator->getAnimationPhases() - 1;
102 }
103 if (!type->isAnimateAlways() && ui) {
104 animationPhase += 1;
105 }
106 }
107 if (g_game.getFeature(Otc::GameWingOffset) && m_wings) {
108 wingBounce();
109 }
110 } else if (animate) {
111 int animationPhases = type->getAnimationPhases();
112 int animateTicks = g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::ITEM_TICKS_PER_FRAME_FAST : Otc::ITEM_TICKS_PER_FRAME;
113
114 if (m_category == ThingCategoryEffect) {
115 animationPhases = std::max<int>(1, animationPhases - 2);
116 animateTicks = g_game.getFeature(Otc::GameEnhancedAnimations) ? Otc::INVISIBLE_TICKS_PER_FRAME_FAST : Otc::INVISIBLE_TICKS_PER_FRAME;
117 }
118
119 if (animationPhases > 1)
120 animationPhase = (g_clock.millis() % (animateTicks * animationPhases)) / animateTicks;
121 if (m_category == ThingCategoryEffect)
122 animationPhase = std::min<int>(animationPhase + 1, animationPhases);
123 }
124
125 int zPattern = m_mount > 0 ? std::min<int>(1, type->getNumPatternZ() - 1) : 0;
126 auto drawMount = [&] {
127 if (zPattern > 0) {
128 int mountAnimationPhase = walkAnimationPhase;
129 auto mountType = g_things.rawGetThingType(m_mount, ThingCategoryCreature);
130 auto idleAnimator = mountType->getIdleAnimator();
131 if (idleAnimator && animate) {
132 if (walkAnimationPhase > 0) {
133 mountAnimationPhase += idleAnimator->getAnimationPhases() - 1;
134 } else {
135 mountAnimationPhase = idleAnimator->getPhase();
136 }
137 }
138 else if (ui && animate) {
139 int phases = mountType->getAnimator() ? mountType->getAnimator()->getAnimationPhases() : mountType->getAnimationPhases();
140 int ticksPerFrame = 1000 / phases;
141 mountAnimationPhase = (g_clock.millis() % (ticksPerFrame * phases)) / ticksPerFrame;
142 if (!mountType->isAnimateAlways()) {
143 mountAnimationPhase += 1;
144 }
145 }
146 if (m_wings && g_game.getFeature(Otc::GameWingOffset)) {
147 if (idleAnimator) {
148 mountAnimationPhase = idleAnimator->getPhase();
149 }
150 else {
151 mountAnimationPhase = 0;
152 }
153 }
154
155 dest -= mountType->getDisplacement() * g_sprites.getOffsetFactor();
156 mountType->draw(dest, 0, direction, 0, 0, mountAnimationPhase, Color::white, lightView);
157 dest += type->getDisplacement() * g_sprites.getOffsetFactor();
158 }
159 };
160
161 auto drawWings = [&] {
162 int wingAnimationPhase = walkAnimationPhase;
163 auto wingsType = g_things.rawGetThingType(m_wings, ThingCategoryCreature);
164 int wingsZPattern = m_mount > 0 ? std::min<int>(1, wingsType->getNumPatternZ() - 1) : 0;
165 auto idleAnimator = wingsType->getIdleAnimator();
166 if (animate) {
167 if (idleAnimator) {
168 if (walkAnimationPhase > 0) {
169 wingAnimationPhase += idleAnimator->getAnimationPhases() - 1;
170 } else {
171 wingAnimationPhase = idleAnimator->getPhase();
172 }
173 } else if (wingsType->isAnimateAlways()) {
174 int phases = wingsType->getAnimator() ? wingsType->getAnimator()->getAnimationPhases() : wingsType->getAnimationPhases();
175 int ticksPerFrame = 1000 / phases;
176 wingAnimationPhase = (g_clock.millis() % (ticksPerFrame * phases)) / ticksPerFrame;
177 }
178 }
179 wingDest.x += wingsOffset.x;
180 wingDest.y += wingsOffset.y;
181 wingsType->draw(wingDest, 0, direction, 0, wingsZPattern, wingAnimationPhase, Color::white, lightView);
182 };
183
184 Point auraDest = dest;
185 auto drawAura = [&] {
186 int auraAnimationPhase = 0;
187 auto auraType = g_things.rawGetThingType(m_aura, ThingCategoryCreature);
188 int auraZPattern = m_mount > 0 ? std::min<int>(1, auraType->getNumPatternZ() - 1) : 0;
189 auto auraAnimator = auraType->getAnimator();
190 if (animate) {
191 if (auraType->isAnimateAlways()) {
192 int phases = auraType->getAnimator() ? auraType->getAnimator()->getAnimationPhases() : auraType->getAnimationPhases();
193 int ticksPerFrame = 1000 / phases;
194 auraAnimationPhase = (g_clock.millis() % (ticksPerFrame * phases)) / ticksPerFrame;
195 }
196 else if (auraAnimator) {
197 auraAnimationPhase = auraAnimator->getPhase();
198 }
199 else {
200 auraAnimationPhase = (stdext::millis() / 75) % auraType->getAnimationPhases();
201 }
202 }
203 auraType->draw(auraDest, 0, direction, 0, auraZPattern, auraAnimationPhase, Color::white, lightView);
204 };
205
206 Point topAuraDest = dest;
207 auto drawTopAura = [&] {
208 int auraAnimationPhase = 0;
209 auto auraType = g_things.rawGetThingType(m_aura, ThingCategoryCreature);
210 auto auraAnimator = auraType->getAnimator();
211 if (animate) {
212 if (auraType->isAnimateAlways()) {
213 int phases = auraType->getAnimator() ? auraType->getAnimator()->getAnimationPhases() : auraType->getAnimationPhases();
214 int ticksPerFrame = 1000 / phases;
215 auraAnimationPhase = (g_clock.millis() % (ticksPerFrame * phases)) / ticksPerFrame;
216 }
217 else if (auraAnimator) {
218 auraAnimationPhase = auraAnimator->getPhase();
219 }
220 else {
221 auraAnimationPhase = (stdext::millis() / 75) % auraType->getAnimationPhases();
222 }
223 }
224 auraType->draw(topAuraDest, 1, direction, 0, 0, auraAnimationPhase, Color::white, lightView);
225 };
226
227 if (m_aura && g_game.getFeature(Otc::GameBigAurasCenter)) {
228 auto auraType = g_things.rawGetThingType(m_aura, ThingCategoryCreature);
229 int auraHeight = auraType->getHeight();
230 int auraWidth = auraType->getWidth();
231 if (auraHeight > 1 || auraWidth > 1) {
232 Point offset = Point (auraWidth > 1 ? (auraWidth - 1) * 16 : 0, auraHeight > 1 ? (auraHeight - 1) * 16 : 0);
233 topAuraDest += offset * g_sprites.getOffsetFactor();
234 auraDest += offset * g_sprites.getOffsetFactor();
235 }
236 }
237
238 if (m_aura && (!g_game.getFeature(Otc::GameDrawAuraOnTop) || g_game.getFeature(Otc::GameAuraFrontAndBack)) ) {
239 drawAura();
240 }
241
242 drawMount();
243
244 if (m_wings && (direction == Otc::South || direction == Otc::East)) {
245 auto wingsType = g_things.rawGetThingType(m_wings, ThingCategoryCreature);
246 if (wingsType) {
247 if (type->hasBones() && wingsType->hasBones()) {
248 auto outfitBones = type->getBones(direction);
249 auto outfitWidth = type->getWidth();
250 auto wingsWidth = wingsType->getWidth();
251 int bonusOffset = std::abs(wingsWidth - outfitWidth) * 32;
252 auto wingsBones = wingsType->getBones(direction);
253 auto boneOffset = Point((outfitBones.x - wingsBones.x) + bonusOffset, (outfitBones.y - wingsBones.y) + bonusOffset);
254
255 if (m_mount > 0) {
256 if (direction == Otc::South) {
257 boneOffset.x -= 7;
258 boneOffset.y -= 17;
259 }
260 else if (direction == Otc::East) {
261 boneOffset.x -= 18;
262 boneOffset.y -= 7;
263 }
264 }
265
266 if (g_game.getFeature(Otc::GameWingOffset) && m_wings)
267 boneOffset -= Point(6, 6);
268
269 wingDest = dest + boneOffset * g_sprites.getOffsetFactor();
270 }
271 }
272 if (g_game.getFeature(Otc::GameWingOffset) && zPattern > 0) {
273 if (direction == Otc::East)
274 wingDest -= Point(6, 2) * g_sprites.getOffsetFactor();
275 else
276 wingDest -= Point(0, 6) * g_sprites.getOffsetFactor();
277 }
278 drawWings();
279 }
280
281 Point center;
282 for (int yPattern = 0; yPattern < type->getNumPatternY(); yPattern++) {
283 if (yPattern > 0 && !(getAddons() & (1 << (yPattern - 1)))) {
284 continue;
285 }
286
287 if (type->getLayers() <= 1) {
288 if (!m_shader.empty()) {
289 std::shared_ptr<DrawOutfitParams> outfitParams = type->drawOutfit(dest, 0, direction, yPattern, zPattern, animationPhase, Color::white, lightView);
290 if (!outfitParams)
291 continue;
292 if (yPattern == 0)
293 center = outfitParams->dest.center();
294 DrawQueueItemTexturedRect* outfit = new DrawQueueItemOutfitWithShader(outfitParams->dest, outfitParams->texture, outfitParams->src, outfitParams->offset, center, 0, m_shader, m_center);
295 g_drawQueue->add(outfit);
296 continue;
297 }
298 type->draw(dest, 0, direction, yPattern, zPattern, animationPhase, Color::white, lightView);
299 continue;
300 }
301
302 uint32_t colors = m_head + (m_body << 8) + (m_legs << 16) + (m_feet << 24);
303 std::shared_ptr<DrawOutfitParams> outfitParams = type->drawOutfit(dest, 1, direction, yPattern, zPattern, animationPhase, Color::white, lightView);
304 if (!outfitParams)
305 continue;
306
307 DrawQueueItemTexturedRect* outfit = nullptr;
308 if (m_shader.empty())
309 outfit = new DrawQueueItemOutfit(outfitParams->dest, outfitParams->texture, outfitParams->src, outfitParams->offset, colors, outfitParams->color, m_center);
310 else {
311 if (yPattern == 0)
312 center = outfitParams->dest.center();
313 outfit = new DrawQueueItemOutfitWithShader(outfitParams->dest, outfitParams->texture, outfitParams->src, outfitParams->offset, center, colors, m_shader, m_center);
314 }
315 g_drawQueue->add(outfit);
316 }
317
318 if (m_wings && (direction == Otc::North || direction == Otc::West)) {
319 auto wingsType = g_things.rawGetThingType(m_wings, ThingCategoryCreature);
320 if (wingsType) {
321 if (type->hasBones() && wingsType->hasBones()) {
322 auto outfitBones = type->getBones(direction);
323 auto outfitWidth = type->getWidth();
324 auto wingsWidth = wingsType->getWidth();
325 int bonusOffset = std::abs(wingsWidth - outfitWidth) * 32;
326 auto wingsBones = wingsType->getBones(direction);
327 auto boneOffset = Point((outfitBones.x - wingsBones.x) + bonusOffset, (outfitBones.y - wingsBones.y) + bonusOffset);
328
329 if (m_mount > 0) {
330 if (direction == Otc::North) {
331 boneOffset.x -= 11;
332 boneOffset.y -= 9;
333 }
334 else if (direction == Otc::West) {
335 boneOffset.x -= 9;
336 boneOffset.y -= 11;
337 }
338 }
339
340 if (g_game.getFeature(Otc::GameWingOffset) && m_wings)
341 boneOffset -= Point(6, 6);
342
343 wingDest = dest + boneOffset * g_sprites.getOffsetFactor();
344 }
345 }
346 if (g_game.getFeature(Otc::GameWingOffset) && zPattern > 0)
347 wingDest += Point(4, 6);
348
349 drawWings();
350 }
351
352 if (m_aura && (g_game.getFeature(Otc::GameDrawAuraOnTop) || g_game.getFeature(Otc::GameAuraFrontAndBack))) {
353 if (g_game.getFeature(Otc::GameAuraFrontAndBack)){
354 if (zPattern > 0) {
355 if (direction == Otc::East)
356 topAuraDest -= Point(12, 6) * g_sprites.getOffsetFactor();
357 else if (direction == Otc::South)
358 topAuraDest -= Point(1, 12) * g_sprites.getOffsetFactor();
359 else
360 topAuraDest -= Point(4, 6) * g_sprites.getOffsetFactor();
361 }
362 drawTopAura();
363 }
364 else {
365 drawAura();
366 }
367 }
368}
369
370void Outfit::draw(const Rect& dest, Otc::Direction direction, uint animationPhase, bool animate, bool ui, bool oldScaling)
371{
372 int size = g_drawQueue->size();
373 draw(Point(0, 0), direction, animationPhase, animate, nullptr, ui);
374 g_drawQueue->correctOutfit(dest, size, oldScaling);
375}
376
377void Outfit::resetClothes()
378{
379 setHead(0);
380 setBody(0);
381 setLegs(0);
382 setFeet(0);
383 setMount(0);
384 setWings(0);
385 setAura(0);
386 resetShader();
387}
388
389// drawing
390
391bool DrawQueueItemOutfit::cache()
392{
393 m_texture->update();
394 uint64_t hash = (((uint64_t)m_texture->getUniqueId()) << 48) +
395 (((uint64_t)m_src.x()) << 36) +
396 (((uint64_t)m_src.y()) << 24) +
397 (((uint64_t)m_src.width()) << 12) +
398 (((uint64_t)m_src.height())) +
399 (((uint64_t)m_colors) * 1125899906842597ULL);
400 bool drawNow = false;
401 Point atlasPos = g_atlas.cache(hash, m_src.size(), drawNow);
402 if (atlasPos.x < 0) { return false; } // can't be cached
403 if (drawNow) { g_drawCache.bind(); draw(atlasPos); }
404
405 if (!g_drawCache.hasSpace(6))
406 return false;
407
408 g_drawCache.addTexturedRect(m_dest, Rect(atlasPos, m_src.size()), m_color);
409 return true;
410}
411
412void DrawQueueItemOutfit::draw()
413{
414 if (!m_texture) return;
415 Matrix4 mat4;
416 for (int x = 0; x < 4; ++x) {
417 Color color = Color::getOutfitColor((m_colors >> (x * 8)) & 0xFF);
418 mat4(x + 1, 1) = color.rF();
419 mat4(x + 1, 2) = color.gF();
420 mat4(x + 1, 3) = color.bF();
421 mat4(x + 1, 4) = color.aF();
422 }
423 g_painter->setDrawOutfitLayersProgram();
424 g_painter->setMatrixColor(mat4);
425 g_painter->setOffset(m_offset);
426 g_painter->drawTexturedRect(m_dest, m_texture, m_src);
427 g_painter->resetShaderProgram();
428}
429
430void DrawQueueItemOutfit::draw(const Point& pos)
431{
432 if (!m_texture) return;
433 Matrix4 mat4;
434 for (int x = 0; x < 4; ++x) {
435 Color color = Color::getOutfitColor((m_colors >> (x * 8)) & 0xFF);
436 mat4(x + 1, 1) = color.rF();
437 mat4(x + 1, 2) = color.gF();
438 mat4(x + 1, 3) = color.bF();
439 mat4(x + 1, 4) = color.aF();
440 }
441 g_painter->setDrawOutfitLayersProgram();
442 g_painter->setMatrixColor(mat4);
443 g_painter->setOffset(m_offset);
444 g_painter->drawTexturedRect(Rect(pos, m_src.size()), m_texture, m_src);
445 g_painter->resetShaderProgram();
446}
447
448void DrawQueueItemOutfitWithShader::draw()
449{
450 if (!m_texture) return;
451 PainterShaderProgramPtr shader = g_shaders.getShader(m_shader);
452 if (!shader) return DrawQueueItemTexturedRect::draw();
453 bool useFramebuffer = m_dest.size() != m_src.size();
454
455 if (useFramebuffer) {
456 g_framebuffers.getTemporaryFrameBuffer()->resize(m_src.size());
457 g_framebuffers.getTemporaryFrameBuffer()->bind();
458 g_painter->clear(Color::alpha);
459 }
460
461 Matrix4 mat4;
462 for (int x = 0; x < 4; ++x) {
463 Color color = Color::getOutfitColor((m_colors >> (x * 8)) & 0xFF);
464 mat4(x + 1, 1) = color.rF();
465 mat4(x + 1, 2) = color.gF();
466 mat4(x + 1, 3) = color.bF();
467 mat4(x + 1, 4) = color.aF();
468 }
469 g_painter->setShaderProgram(shader);
470 g_painter->setOffset(m_offset);
471 shader->setMatrixColor(mat4);
472 shader->setCenter(m_center);
473 shader->bindMultiTextures();
474 if (useFramebuffer) {
475 g_painter->drawTexturedRect(Rect(0, 0, m_src.size()), m_texture, m_src);
476 } else {
477 g_painter->drawTexturedRect(m_dest, m_texture, m_src);
478 }
479 g_painter->resetShaderProgram();
480
481 if (useFramebuffer) {
482 g_framebuffers.getTemporaryFrameBuffer()->release();
483 g_painter->resetColor();
484 g_framebuffers.getTemporaryFrameBuffer()->draw(m_dest);
485 }
486}