From 24edfa07ec63c43d9bbd3a1788195bf5f13b3dff Mon Sep 17 00:00:00 2001 From: Eugene Sandulenko Date: Mon, 2 Sep 2019 23:51:45 +0200 Subject: GRIFFON: Added gfx.cpp --- engines/griffon/engine.cpp | 461 +++++++++++++++++---------------------------- engines/griffon/gfx.cpp | 171 +++++++++++++++++ engines/griffon/griffon.h | 39 ++-- engines/griffon/module.mk | 1 + engines/griffon/sound.cpp | 12 -- 5 files changed, 366 insertions(+), 318 deletions(-) create mode 100644 engines/griffon/gfx.cpp (limited to 'engines/griffon') diff --git a/engines/griffon/engine.cpp b/engines/griffon/engine.cpp index 1c7cac9fff..8809e8c817 100644 --- a/engines/griffon/engine.cpp +++ b/engines/griffon/engine.cpp @@ -98,92 +98,206 @@ float GriffonEngine::RND() { return (float)_rnd->getRandomNumber(32767) * (1.0f / 32768.0f); } -void GriffonEngine::addFloatIcon(int ico, float xloc, float yloc) { - for (int i = 0; i < kMaxFloat; i++) { - if (ABS(_floaticon[i][0]) < kEpsilon) { - _floaticon[i][0] = 32; - _floaticon[i][1] = xloc; - _floaticon[i][2] = yloc; - _floaticon[i][3] = ico; - return; - } +void GriffonEngine::mainLoop() { + swash(); + + if (_pmenu) { + haltSoundChannel(_menuchannel); + _pmenu = false; } + + do { + if (!_forcepause) { + updateAnims(); + updateNPCs(); + } + + checkTrigger(); + checkInputs(); + + if (!_forcepause) + handleWalking(); + + updateY(); + drawView(); + + updateMusic(); + + _console->onFrame(); + + updateEngine(); + } while (!_shouldQuit); } -void GriffonEngine::addFloatText(const char *stri, float xloc, float yloc, int col) { +void GriffonEngine::updateEngine() { + g_system->updateScreen(); + g_system->getEventManager()->pollEvent(_event); + + _tickspassed = _ticks; + _ticks = g_system->getMillis(); + + _tickspassed = _ticks - _tickspassed; + _fpsr = (float)_tickspassed / 24.0; + + _fp++; + if (_ticks > _nextticks) { + _nextticks = _ticks + 1000; + _fps = _fp; + _fp = 0; + _secsingame = _secsingame + 1; + } + + if (attacking) { + _player.attackframe += _player.attackspd * _fpsr; + if (_player.attackframe >= 16) { + attacking = false; + _player.attackframe = 0; + _player.walkframe = 0; + } + + int pa = (int)(_player.attackframe); + + for (int i = 0; i <= pa; i++) { + if (ABS(_playerattackofs[_player.walkdir][i][2]) < kEpsilon) { + _playerattackofs[_player.walkdir][i][2] = 1; + + float opx = _player.px; + float opy = _player.py; + + _player.px = _player.px + _playerattackofs[_player.walkdir][i][0]; + _player.py = _player.py + _playerattackofs[_player.walkdir][i][1]; + + int sx = (int)(_player.px / 2 + 6); + int sy = (int)(_player.py / 2 + 10); + uint32 *temp = (uint32 *)_clipbg->getBasePtr(sx, sy); + uint32 bgc = *temp; + if (bgc > 0) { + _player.px = opx; + _player.py = opy; + } + } + } + + _player.opx = _player.px; + _player.opy = _player.py; + + checkHit(); + } + for (int i = 0; i < kMaxFloat; i++) { - if (ABS(_floattext[i][0]) < kEpsilon) { - _floattext[i][0] = 32; - _floattext[i][1] = xloc; - _floattext[i][2] = yloc; - _floattext[i][3] = col; - strcpy(_floatstri[i], stri); - return; + if (_floattext[i][0] > 0) { + float spd = 0.5 * _fpsr; + _floattext[i][0] = _floattext[i][0] - spd; + _floattext[i][2] = _floattext[i][2] - spd; + if (_floattext[i][0] < 0) + _floattext[i][0] = 0; + } + + if (_floaticon[i][0] > 0) { + float spd = 0.5 * _fpsr; + _floaticon[i][0] = _floaticon[i][0] - spd; + _floaticon[i][2] = _floaticon[i][2] - spd; + if (_floaticon[i][0] < 0) + _floaticon[i][0] = 0; } } -} -void GriffonEngine::eventText(const char *stri) { - _videobuffer2->fillRect(Common::Rect(0, 0, _videobuffer2->w, _videobuffer2->h), 0); - _videobuffer3->fillRect(Common::Rect(0, 0, _videobuffer3->w, _videobuffer3->h), 0); + if (_player.level == _player.maxlevel) + _player.exp = 0; - int x = 160 - 4 * strlen(stri); + if (_player.exp >= _player.nextlevel) { + _player.level = _player.level + 1; + addFloatText("LEVEL UP!", _player.px + 16 - 36, _player.py + 16, 3); + _player.exp = _player.exp - _player.nextlevel; + _player.nextlevel = _player.nextlevel * 3 / 2; // 1.5 + _player.maxhp = _player.maxhp + _player.level * 3; + if (_player.maxhp > 999) + _player.maxhp = 999; + _player.hp = _player.maxhp; - _ticks = g_system->getMillis(); - int pause_ticks = _ticks + 500; - int b_ticks = _ticks; + _player.sworddamage = _player.level * 14 / 10; + _player.spelldamage = _player.level * 13 / 10; - _videobuffer->blit(*_videobuffer3); - _videobuffer->blit(*_videobuffer2); + if (config.effects) { + int snd = playSound(_sfx[kSndPowerUp]); + setChannelVolume(snd, config.effectsvol); + } + } - do { - g_system->getEventManager()->pollEvent(_event); + _clipbg->copyRectToSurface(_clipbg2->getPixels(), _clipbg2->pitch, 0, 0, _clipbg2->w, _clipbg2->h); - if (_event.type == Common::EVENT_KEYDOWN && pause_ticks < _ticks) - break; - _videobuffer2->blit(*_videobuffer); + Common::Rect rc; - int fr = 192; + rc.left = _player.px - 2; + rc.top = _player.py - 2; + rc.setWidth(5); + rc.setHeight(5); + + _clipbg->fillRect(rc, 1000); + + if (!_forcepause) { + for (int i = 0; i < 5; i++) { + if (_player.foundspell[i] == 1) + _player.spellcharge[i] += 1 * _player.level * 0.01 * _fpsr; + if (_player.spellcharge[i] > 100) + _player.spellcharge[i] = 100; + } + + if (_player.foundspell[0]) { + _player.spellstrength += 3 * _player.level * .01 * _fpsr; + } - if (pause_ticks > _ticks) - fr = 192 * (_ticks - b_ticks) / 500; - if (fr > 192) - fr = 192; + _player.attackstrength += (30 + 3 * (float)_player.level) / 50 * _fpsr; + } - _windowimg->setAlpha(fr, true); + if (_player.attackstrength > 100) + _player.attackstrength = 100; - _windowimg->blit(*_videobuffer); - if (pause_ticks < _ticks) - drawString(_videobuffer, stri, x, 15, 0); + if (_player.spellstrength > 100) + _player.spellstrength = 100; - g_system->copyRectToScreen(_videobuffer->getPixels(), _videobuffer->pitch, 0, 0, _videobuffer->w, _videobuffer->h); - g_system->updateScreen(); + _itemyloc += 0.75 * _fpsr; + while (_itemyloc >= 16) + _itemyloc -= 16; - g_system->getEventManager()->pollEvent(_event); - g_system->delayMillis(10); + if (_player.hp <= 0) + theEnd(); - _tickspassed = _ticks; - _ticks = g_system->getMillis(); + if (_roomlock) { + _roomlock = false; + for (int i = 1; i <= _lastnpc; i++) + if (_npcinfo[i].hp > 0) + _roomlock = true; + } - _tickspassed = _ticks - _tickspassed; - _fpsr = (float)_tickspassed / 24.0; + clouddeg += 0.1 * _fpsr; + while (clouddeg >= 360) + clouddeg = clouddeg - 360; - _fp++; - if (_ticks > _nextticks) { - _nextticks = _ticks + 1000; - _fps = _fp; - _fp = 0; + _player.hpflash = _player.hpflash + 0.1 * _fpsr; + if (_player.hpflash >= 2) { + _player.hpflash = 0; + _player.hpflashb = _player.hpflashb + 1; + if (_player.hpflashb == 2) + _player.hpflashb = 0; + if (config.effects && _player.hpflashb == 0 && _player.hp < _player.maxhp / 4) { + int snd = playSound(_sfx[kSndBeep]); + setChannelVolume(snd, config.effectsvol); } + } - g_system->delayMillis(10); - } while (1); + // cloudson = 0 - _videobuffer3->blit(*_videobuffer); + if (_itemselon == 1) + _player.itemselshade = _player.itemselshade + 2 * _fpsr; + if (_player.itemselshade > 24) + _player.itemselshade = 24; - _itemticks = _ticks + 210; + for (int i = 0; i <= 4; i++) + if (_player.inventory[i] > 9) + _player.inventory[i] = 9; } - void GriffonEngine::newGame() { intro(); @@ -262,37 +376,6 @@ void GriffonEngine::newGame() { mainLoop(); } -void GriffonEngine::mainLoop() { - swash(); - - if (_pmenu) { - haltSoundChannel(_menuchannel); - _pmenu = false; - } - - do { - if (!_forcepause) { - updateAnims(); - updateNPCs(); - } - - checkTrigger(); - checkInputs(); - - if (!_forcepause) - handleWalking(); - - updateY(); - drawView(); - - updateMusic(); - - _console->onFrame(); - - updateEngine(); - } while (!_shouldQuit); -} - void GriffonEngine::updateAnims() { for (int i = 0; i <= _lastObj; i++) { int nframes = _objectInfo[i][0]; @@ -2613,203 +2696,5 @@ void GriffonEngine::updateSpellsUnder() { } } -void GriffonEngine::drawLine(Graphics::TransparentSurface *buffer, int x1, int y1, int x2, int y2, int col) { - int xdif = x2 - x1; - int ydif = y2 - y1; - - if (xdif == 0) { - for (int y = y1; y <= y2; y++) { - uint32 *temp = (uint32 *)buffer->getBasePtr(x1, y); - *temp = col; - } - } - - if (ydif == 0) { - for (int x = x1; x <= x2; x++) { - uint32 *temp = (uint32 *)buffer->getBasePtr(x, y1); - *temp = col; - } - } -} - -void GriffonEngine::drawString(Graphics::TransparentSurface *buffer, const char *stri, int xloc, int yloc, int col) { - int l = strlen(stri); - - for (int i = 0; i < l; i++) { - rcDest.left = xloc + i * 8; - rcDest.top = yloc; - - _fontchr[stri[i] - 32][col]->blit(*buffer, rcDest.left, rcDest.top); - } -} - -void GriffonEngine::updateEngine() { - g_system->updateScreen(); - g_system->getEventManager()->pollEvent(_event); - - _tickspassed = _ticks; - _ticks = g_system->getMillis(); - - _tickspassed = _ticks - _tickspassed; - _fpsr = (float)_tickspassed / 24.0; - - _fp++; - if (_ticks > _nextticks) { - _nextticks = _ticks + 1000; - _fps = _fp; - _fp = 0; - _secsingame = _secsingame + 1; - } - - if (attacking) { - _player.attackframe += _player.attackspd * _fpsr; - if (_player.attackframe >= 16) { - attacking = false; - _player.attackframe = 0; - _player.walkframe = 0; - } - - int pa = (int)(_player.attackframe); - - for (int i = 0; i <= pa; i++) { - if (ABS(_playerattackofs[_player.walkdir][i][2]) < kEpsilon) { - _playerattackofs[_player.walkdir][i][2] = 1; - - float opx = _player.px; - float opy = _player.py; - - _player.px = _player.px + _playerattackofs[_player.walkdir][i][0]; - _player.py = _player.py + _playerattackofs[_player.walkdir][i][1]; - - int sx = (int)(_player.px / 2 + 6); - int sy = (int)(_player.py / 2 + 10); - uint32 *temp = (uint32 *)_clipbg->getBasePtr(sx, sy); - uint32 bgc = *temp; - if (bgc > 0) { - _player.px = opx; - _player.py = opy; - } - } - } - - _player.opx = _player.px; - _player.opy = _player.py; - - checkHit(); - } - - for (int i = 0; i < kMaxFloat; i++) { - if (_floattext[i][0] > 0) { - float spd = 0.5 * _fpsr; - _floattext[i][0] = _floattext[i][0] - spd; - _floattext[i][2] = _floattext[i][2] - spd; - if (_floattext[i][0] < 0) - _floattext[i][0] = 0; - } - - if (_floaticon[i][0] > 0) { - float spd = 0.5 * _fpsr; - _floaticon[i][0] = _floaticon[i][0] - spd; - _floaticon[i][2] = _floaticon[i][2] - spd; - if (_floaticon[i][0] < 0) - _floaticon[i][0] = 0; - } - } - - if (_player.level == _player.maxlevel) - _player.exp = 0; - - if (_player.exp >= _player.nextlevel) { - _player.level = _player.level + 1; - addFloatText("LEVEL UP!", _player.px + 16 - 36, _player.py + 16, 3); - _player.exp = _player.exp - _player.nextlevel; - _player.nextlevel = _player.nextlevel * 3 / 2; // 1.5 - _player.maxhp = _player.maxhp + _player.level * 3; - if (_player.maxhp > 999) - _player.maxhp = 999; - _player.hp = _player.maxhp; - - _player.sworddamage = _player.level * 14 / 10; - _player.spelldamage = _player.level * 13 / 10; - - if (config.effects) { - int snd = playSound(_sfx[kSndPowerUp]); - setChannelVolume(snd, config.effectsvol); - } - } - - _clipbg->copyRectToSurface(_clipbg2->getPixels(), _clipbg2->pitch, 0, 0, _clipbg2->w, _clipbg2->h); - - Common::Rect rc; - - rc.left = _player.px - 2; - rc.top = _player.py - 2; - rc.setWidth(5); - rc.setHeight(5); - - _clipbg->fillRect(rc, 1000); - - if (!_forcepause) { - for (int i = 0; i < 5; i++) { - if (_player.foundspell[i] == 1) - _player.spellcharge[i] += 1 * _player.level * 0.01 * _fpsr; - if (_player.spellcharge[i] > 100) - _player.spellcharge[i] = 100; - } - - if (_player.foundspell[0]) { - _player.spellstrength += 3 * _player.level * .01 * _fpsr; - } - - _player.attackstrength += (30 + 3 * (float)_player.level) / 50 * _fpsr; - } - - if (_player.attackstrength > 100) - _player.attackstrength = 100; - - if (_player.spellstrength > 100) - _player.spellstrength = 100; - - _itemyloc += 0.75 * _fpsr; - while (_itemyloc >= 16) - _itemyloc -= 16; - - if (_player.hp <= 0) - theEnd(); - - if (_roomlock) { - _roomlock = false; - for (int i = 1; i <= _lastnpc; i++) - if (_npcinfo[i].hp > 0) - _roomlock = true; - } - - clouddeg += 0.1 * _fpsr; - while (clouddeg >= 360) - clouddeg = clouddeg - 360; - - _player.hpflash = _player.hpflash + 0.1 * _fpsr; - if (_player.hpflash >= 2) { - _player.hpflash = 0; - _player.hpflashb = _player.hpflashb + 1; - if (_player.hpflashb == 2) - _player.hpflashb = 0; - if (config.effects && _player.hpflashb == 0 && _player.hp < _player.maxhp / 4) { - int snd = playSound(_sfx[kSndBeep]); - setChannelVolume(snd, config.effectsvol); - } - } - - // cloudson = 0 - - if (_itemselon == 1) - _player.itemselshade = _player.itemselshade + 2 * _fpsr; - if (_player.itemselshade > 24) - _player.itemselshade = 24; - - for (int i = 0; i <= 4; i++) - if (_player.inventory[i] > 9) - _player.inventory[i] = 9; -} } // end of namespace Griffon diff --git a/engines/griffon/gfx.cpp b/engines/griffon/gfx.cpp new file mode 100644 index 0000000000..31dfc6a73f --- /dev/null +++ b/engines/griffon/gfx.cpp @@ -0,0 +1,171 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * + * Originally written by Syn9 in FreeBASIC with SDL + * http://syn9.thehideoutgames.com/index_backup.php + * + * Ported to plain C for GCW-Zero handheld by Dmitry Smagin + * http://github.com/dmitrysmagin/griffon_legend + * + * + * Programming/Graphics: Daniel "Syn9" Kennedy + * Music/Sound effects: David Turner + * + * Beta testing and gameplay design help: + * Deleter, Cha0s, Aether Fox, and Kiz + * + */ + +#include "common/system.h" + +#include "griffon/griffon.h" + +namespace Griffon { + +void GriffonEngine::addFloatIcon(int ico, float xloc, float yloc) { + for (int i = 0; i < kMaxFloat; i++) { + if (ABS(_floaticon[i][0]) < kEpsilon) { + _floaticon[i][0] = 32; + _floaticon[i][1] = xloc; + _floaticon[i][2] = yloc; + _floaticon[i][3] = ico; + return; + } + } +} + +void GriffonEngine::addFloatText(const char *stri, float xloc, float yloc, int col) { + for (int i = 0; i < kMaxFloat; i++) { + if (ABS(_floattext[i][0]) < kEpsilon) { + _floattext[i][0] = 32; + _floattext[i][1] = xloc; + _floattext[i][2] = yloc; + _floattext[i][3] = col; + strcpy(_floatstri[i], stri); + return; + } + } +} + +void GriffonEngine::eventText(const char *stri) { + _videobuffer2->fillRect(Common::Rect(0, 0, _videobuffer2->w, _videobuffer2->h), 0); + _videobuffer3->fillRect(Common::Rect(0, 0, _videobuffer3->w, _videobuffer3->h), 0); + + int x = 160 - 4 * strlen(stri); + + _ticks = g_system->getMillis(); + int pause_ticks = _ticks + 500; + int b_ticks = _ticks; + + _videobuffer->blit(*_videobuffer3); + _videobuffer->blit(*_videobuffer2); + + do { + g_system->getEventManager()->pollEvent(_event); + + if (_event.type == Common::EVENT_KEYDOWN && pause_ticks < _ticks) + break; + _videobuffer2->blit(*_videobuffer); + + int fr = 192; + + if (pause_ticks > _ticks) + fr = 192 * (_ticks - b_ticks) / 500; + if (fr > 192) + fr = 192; + + _windowimg->setAlpha(fr, true); + + _windowimg->blit(*_videobuffer); + if (pause_ticks < _ticks) + drawString(_videobuffer, stri, x, 15, 0); + + g_system->copyRectToScreen(_videobuffer->getPixels(), _videobuffer->pitch, 0, 0, _videobuffer->w, _videobuffer->h); + g_system->updateScreen(); + + g_system->getEventManager()->pollEvent(_event); + g_system->delayMillis(10); + + _tickspassed = _ticks; + _ticks = g_system->getMillis(); + + _tickspassed = _ticks - _tickspassed; + _fpsr = (float)_tickspassed / 24.0; + + _fp++; + if (_ticks > _nextticks) { + _nextticks = _ticks + 1000; + _fps = _fp; + _fp = 0; + } + + g_system->delayMillis(10); + } while (1); + + _videobuffer3->blit(*_videobuffer); + + _itemticks = _ticks + 210; +} + +void GriffonEngine::drawLine(Graphics::TransparentSurface *buffer, int x1, int y1, int x2, int y2, int col) { + int xdif = x2 - x1; + int ydif = y2 - y1; + + if (xdif == 0) { + for (int y = y1; y <= y2; y++) { + uint32 *temp = (uint32 *)buffer->getBasePtr(x1, y); + *temp = col; + } + } + + if (ydif == 0) { + for (int x = x1; x <= x2; x++) { + uint32 *temp = (uint32 *)buffer->getBasePtr(x, y1); + *temp = col; + } + } +} + +void GriffonEngine::drawString(Graphics::TransparentSurface *buffer, const char *stri, int xloc, int yloc, int col) { + int l = strlen(stri); + + for (int i = 0; i < l; i++) { + rcDest.left = xloc + i * 8; + rcDest.top = yloc; + + _fontchr[stri[i] - 32][col]->blit(*buffer, rcDest.left, rcDest.top); + } +} + +void GriffonEngine::drawProgress(int w, int wm) { + long ccc = _videobuffer->format.RGBToColor(0, 255, 0); + + rcDest.setWidth(w * 74 / wm); + _videobuffer->fillRect(rcDest, ccc); + + g_system->copyRectToScreen(_videobuffer->getPixels(), _videobuffer->pitch, 0, 0, _videobuffer->w, _videobuffer->h); + g_system->updateScreen(); + + g_system->getEventManager()->pollEvent(_event); +} + + +} // end of namespace Griffon diff --git a/engines/griffon/griffon.h b/engines/griffon/griffon.h index 26fe9b684f..e5b98adc14 100644 --- a/engines/griffon/griffon.h +++ b/engines/griffon/griffon.h @@ -306,6 +306,27 @@ private: void drawView(); void swash(); + // engine.cpp + float RND(); + + void newGame(); + void mainLoop(); + void updateAnims(); + void updateY(); + void updateNPCs(); + void updateSpells(); + void updateSpellsUnder(); + + void updateEngine(); + + // gfx.cpp + void addFloatIcon(int ico, float xloc, float yloc); + void addFloatText(const char *stri, float xloc, float yloc, int col); + void eventText(const char *stri); + void drawLine(Graphics::TransparentSurface *buffer, int x1, int y1, int x2, int y2, int col); + void drawString(Graphics::TransparentSurface *buffer, const char *stri, int xloc, int yloc, int col); + void drawProgress(int w, int wm); + // input.cpp void checkInputs(); void handleWalking(); @@ -340,24 +361,6 @@ private: void setupAudio(); void updateMusic(); - float RND(); - - void addFloatIcon(int ico, float xloc, float yloc); - void addFloatText(const char *stri, float xloc, float yloc, int col); - void eventText(const char *stri); - void newGame(); - void mainLoop(); - void updateAnims(); - void updateY(); - void updateNPCs(); - void updateSpells(); - void updateSpellsUnder(); - - void drawLine(Graphics::TransparentSurface *buffer, int x1, int y1, int x2, int y2, int col); - void drawString(Graphics::TransparentSurface *buffer, const char *stri, int xloc, int yloc, int col); - void drawProgress(int w, int wm); - void updateEngine(); - private: Graphics::TransparentSurface *_video, *_videobuffer, *_videobuffer2, *_videobuffer3; diff --git a/engines/griffon/module.mk b/engines/griffon/module.mk index 4eecec679d..43a198b99d 100644 --- a/engines/griffon/module.mk +++ b/engines/griffon/module.mk @@ -9,6 +9,7 @@ MODULE_OBJS := \ dialogs.o \ draw.o \ engine.o \ + gfx.o \ griffon.o \ input.o \ resources.o \ diff --git a/engines/griffon/sound.cpp b/engines/griffon/sound.cpp index eaa99c3038..f488c5b757 100644 --- a/engines/griffon/sound.cpp +++ b/engines/griffon/sound.cpp @@ -111,18 +111,6 @@ DataChunk *cacheSound(const char *name) { return res; } -void GriffonEngine::drawProgress(int w, int wm) { - long ccc = _videobuffer->format.RGBToColor(0, 255, 0); - - rcDest.setWidth(w * 74 / wm); - _videobuffer->fillRect(rcDest, ccc); - - g_system->copyRectToScreen(_videobuffer->getPixels(), _videobuffer->pitch, 0, 0, _videobuffer->w, _videobuffer->h); - g_system->updateScreen(); - - g_system->getEventManager()->pollEvent(_event); -} - void GriffonEngine::setupAudio() { // FIXME //Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 1024); -- cgit v1.2.3