Git Product home page Git Product logo

ko4life-net / ko Goto Github PK

View Code? Open in Web Editor NEW
45.0 11.0 21.0 42.25 MB

Open source development of the game Knight Online. This is a reversed engineered old version of the game aiming to replicate the nostalgic experience we all once had <3

License: MIT License

C++ 97.36% C 2.17% HTML 0.17% Makefile 0.28% Batchfile 0.01% PowerShell 0.01%
3d-graphics directx game knight-online mmorpg tcp-client-server

ko's Introduction

Knight Online

This is the official Knight Online repository containing sources for the game, server and tools.

This is the main project which focuses only on source development. Its corresponding submodules are fetched automatically via the config.cmd script and maintained under the following projects:

Prerequisite

  • Visual Studio 2022: The project may be built with older Visual Studio versions, however for the sake of consistency, please install Visual Studio 2022. You will also need to install additional MSVC, MFC and ATL components if you haven't already. The way it's done in Visual Studio 2022 is by:
    • Click on Tool -> Get Tools and Features...
    • In the new window switch to Individual components tab
    • Search for mfc and select C++ MFC for latest v143 build tools (x86 & x64)
    • Search for atl and select C++ ATL for latest v143 build tools (x86 & x64)
    • Search for msvc and select MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
    • Click on Modify to start the installation
  • Microsoft SQL Server Express or Developer (confirmed to be working with 2008 and 2022)
  • Git: https://git-scm.com/download/win

Getting Started

To get started, clone this repository:

git clone https://github.com/ko4life-net/ko.git

and then double click on the config.cmd script in the root directory.

After configuring the project is done, you can now start the Visual Studio solution (*.sln) file of your preference under the src directory.

Code formatting / Linting

This project is using clang-format from LLVM project to ensure consistency in the entire codebase and to make it easier for everyone to contribute.

When a pull-request is created, automated checks will test your changes to check for possible linting issues. Your pull-request will not be reviewed or considered to be merged if the linter checks are failing.

Therefore and to avoid further confusion, please run the format.ps1 script before submitting your code.

Visual Studio has baked-in support for clang-format and will automatically detect the .clang-format file. However it uses an older version of clang-format, which is not supporting all of the configurations we use. You may want to configure your Visual Studio to the clang-format we have in ko-vendor. Please follow the following steps to do so:

  • Go to Tools -> Options -> Search clang-format
  • Tick: Use custom clang-format.exe and browse the path to src/vendor/opt/bin/clang-format.exe

Now whenever you hit CTRL + K / D keyboard shortcut, clang-format will automatically format your code as configured in this project.

ko's People

Contributors

girx8 avatar janekcode avatar knightguard-ko avatar selcukcukur avatar srmeier avatar stevewgr avatar twostars avatar utengine avatar xgutek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ko's Issues

Implement zone entry notices

Description

Implement zone entry notices, when walking into specific area, for example Arena, it shows yellow text in front top which area we entered.

Tasks

  • This requires loading *.evtsub files
  • Implementing CDFontFade that allows to render text that fades out
  • Also CEventSubManager is required to be implemented

Package and update zlib to latest

Description

There are multiple projects that depends on zlib compression library. It's currently within the project in binary form and built with old compiler configurations. Therefore to create a clean build, we'll need to:

  1. update it, see that nothing breaks.
  2. package it, to it's easy to update as needed. we could put it in ko-vendor repo.

Implement whisper / private chat UIF

Description

Implement whisper / private chat UIF.

Currently there is no implementation for this and probably this requires some server side implementation.

Tasks

  • Implement whisper / private chat UIF

Implement in game exit menu

Description

Implement in game exit menu.

This is the menu where we're already in game and want to exit, we can either:

  1. select character
  2. open options and exit
  3. exit

and maybe there was another one that I don't remember.

Tasks

  • Implement in game exit menu
  • Ensure that if the player is attacked, they need to wait 10 seconds as per official behavior

UIE doesn't load all UI elements properly

Description

UIE doesn't load all UI elements properly.

To Reproduce

Trying loading Co_nationselect_en.uif.
Some text elements are not loaded properly.

Tasks

  • Make UIE load all UI elements properly

Access of scoped iteration variables

Description

As part of the update towards a newer compiler, I temporarily enabled ForceConformanceInForLoopScope in order to separate things.

This property will be deprecated soon and we need to prevent access of variables outside the scope of the for loop.

Currently this is considered as 95% done in this PR: #33

Discuss new audio library implementation for the game engine

Description

Note that currently the existing game engine implementation can only load WAV audio files. This implementation can be found in CWaveFile class.

We need to start thinking about a new Audio library for this game engine.

There are multiple reasons as to why. The official game in v1298 client introduced Miles Sound System SDK coming from mss32.dll in order to start migrating from WAV files to MP3 considering WAV files are big in size and also affects performance due to load time.

Eventually the official game in the latest versions started migrating towards OGG audio files using OpenAL which there couple couple of nice docs for it. Going with OpenAL and converting all audio files to OGG reduced the size and improved performance noticeably in the official client.

We can use this free fork of OpenAL: https://github.com/kcat/openal-soft

Another alternative is FFMpeg: https://github.com/FFmpeg/FFmpeg

Here is a nice discussion: https://stackoverflow.com/questions/12915892/library-for-reading-audio-files

Even though I suggest going for OpenAL as it will also make it work as in the official game, this ticket is open for discussion.

Replace sprintf usages with C++20's std::format

Description

Searching for instances of sprintf in code, you will see that there are some usages where we create a C char array in order to format it with sprintf and then copy it to std::string.
This is wasteful and not intiutive, especially when sprintf function is vulnerable to buffer-overflow exploits.
C++20's std::format comes built-in into the standard: https://en.cppreference.com/w/cpp/utility/format/format
Adapted from the old and famous fmt library: https://github.com/fmtlib/fmt

Apart from making the code more elegant, it also wastes less resources and often increase in speed.

However note that the project currently uses C++17 and migrating from C++17 to C++20 brings more strict constructs to the code base. So first we need to safely upgrade to C++20 before we can tackle this ticket.
Some WIP is done here: https://github.com/stevewgr/ko/tree/cpp20

Support multi direct3d adapter back buffer formats

Description

Note that N3Eng.cpp may initialize the device with any of the following formats:

if(16 == dwBPP) BBFormat = D3DFMT_R5G6B5;
else if(24 == dwBPP) BBFormat = D3DFMT_R8G8B8;
else if(32 == dwBPP) BBFormat = D3DFMT_X8R8G8B8;

depending on the ColorDepth configured in Options.ini under ViewPort section.

The default value is 16, but it may be 24 or 32 as well.

With DirectX-9 upgrade, an additional argument is needed to be provided to GetAdapterModeCount which specify the format of the adapter. For now it's initialized to D3DFMT_X8R8G8B8 as default format assuming the chosen ColorDepth by the user is 32. However we'll need a more dynamic implementation for this and retrieve all adapters with all the possible formats. For example:

int iAMC1 = m_lpD3D->GetAdapterModeCount(0, D3DFMT_X8R8G8B8);
int iAMC2 = m_lpD3D->GetAdapterModeCount(0, D3DFMT_R8G8B8);
int iAMC3 = m_lpD3D->GetAdapterModeCount(0, D3DFMT_R5G6B5);
int iTotalAMC = iAMC1 + iAMC2 + iAMC3;

Then iterate through the adapter count to get all the available depth stencil formats (FindDepthStencilFormat) to make this implementation more appriopriate for the use case.

Engine sets working directory based on exe

Description

Engine sets working directory based on exe. This behavior should be changed in order to make the working directory more flexible. One may run the game from a different directory, as in the case of debugging in Visual Studio.
This messes up with stuff, like loading files correctly and such.

To Reproduce

Run SkyViewer editor in debug mode for example.
Or put the SkyViewer.exe in game directory and run from there.

Tasks

Don't set the working directory based on the exe.

Support NTF7 encrypted textures

Description

Note that newer textures are encrypted. They're classified as NTF7 in the header.
For making the game sources more flexible and adapt some content from different versions, whilst still keeping it old school, we need to add support for this.

Tasks

  • Add support for NTF7 texture encryption.

Implement Zone effects (*.opdsub)

Description

The client still doesn't know about OPDSUB file format in order to load zone effects.

Tasks

  • Load OPDSUB file format
  • Hook it into CN3ShapeMgr render implementation

Integrate ImGui into the project

Description

ImGui is a cross-platform, free and open-source GUI library that we can use for debugging, as well as for other possible features.
It will be nice integrating it, since there are some debug texts that mess up with the UI's positions in game.

Dialogs can also be docked and dragged outside the game window, which is super useful.

Note that I've already done this, but doing a few clean-ups before I can create a PR for this.

Tasks

The following tasks should follow:

  • Integrate ImGui into the project
  • Remove statistics from in-game UI and migrate it to ImGui instead

Implement DirectX device caching mechanism

Description

Note that newer clients implement CN3Device to wrap around every DirectX call.

This is really helpful when wanting to cache certain resources or specific DirectX calls.

This is also helpful when writing statistics and calculating draw calls with the need for 3rd party tools.

Tasks

  • Implement a wrapper around all DirectX-9 calls called CN3Device
  • Cache some of the draw calls, especially setting render states could be cached
  • Replace the 3D device pointer with CN3Device wrapper

Produce sources from implode, jpeg and JvCryption libraries

Description

Part of the migration to modern compilers: #30
The implode, jpeg and JvCryption libraries are used by multiple projects and compiled with old configurations.
Due to this, it forces the project configurations to be build with ImageHasSafeExceptionHandlers set to false, as well as ignore libc.lib to prevent linking errors.

Therefore it will be ideal to get ride of depends so that everything is built with the same build configurations to have a clean build.

Tasks

  • Build jpeg.lib from source
  • Build implode from source
  • Build JvCryption from source
  • Get ride of ImageHasSafeExceptionHandlers in Release builds
  • Remove ignore libc.lib in project configs

News Ebenezer GM Command +summonuser and +giveitem

I opened this topic because I couldn't add coding

user.h

BOOL SpecialOperatorCommand(char* chatstr, int chatlen);

user.cpp

BOOL CUser::SpecialOperatorCommand(char* chatstr, int chatlen)
{
	CUser* pUser = NULL;
	_START_POSITION* pPositionInfo = NULL;

	int send_index = 0;
	char send_buff[1024]; memset(send_buff, NULL, 1024);

	char temp[11]; char struser[21]; char strindex[10];
	int parse_index = 0, zoneindex = -1, izone = 0, inum = 0, icount = 1, iexp = 0, igold = 0; short x = 0, z = 0;

	if (_strnicmp("+summonuser", chatstr, 11) == 0) {
		if (m_pUserData->m_bAuthority != 0) return FALSE;

		parse_index += ParseSpace(temp, chatstr + parse_index); // Command
		parse_index += ParseSpace(struser, chatstr + parse_index); // UserID

		pUser = (CUser*)m_pMain->GetUserPtr(struser, 0x02);
		if (!pUser) return FALSE;

		pUser->ZoneChange(m_pUserData->m_bZone, m_pUserData->m_curx, m_pUserData->m_curz);

		return TRUE;
	}

else if (_strnicmp("+giveitem", chatstr, 9) == 0) {
		if (m_pUserData->m_bAuthority != 0) return FALSE;

		parse_index += ParseSpace(temp, chatstr + parse_index); // Command
		parse_index += ParseSpace(struser, chatstr + parse_index); // UserID
		parse_index += ParseSpace(strindex, chatstr + parse_index); inum = atoi(strindex); // ItemID
		parse_index += ParseSpace(strindex, chatstr + parse_index); icount = atoi(strindex); // ItemCount

		pUser = (CUser*)m_pMain->GetUserPtr(struser, 0x02);
		if (!pUser) return FALSE;

		pUser->GiveItem(inum, icount);

		return TRUE;
	}
	return FALSE;
}

Convert all varchar db columns used as binary to binary format

Description

Note that there are some columns in database being read as binary, but specify column data type as varchar.

This causes other issues, as it breaks scripting when special characters are produced from the binary format, varchar is being treated as string.

Tasks

  • Convert USERDATA strSkill and strItem to binary
  • Update all dependents stored procedures to use binary format
  • Verify that server code reads it right
  • Produce a migration script and a PR for ko-db repo

Translate all code comments to English and convert to UTF8

Description

The encoding topic has always been annoying from the beginning. We can fix this with an automated script and some regex magic.

Also thanks to AI and ML, Google Translate's translation has become quite advanced and accurate.

Tasks

The following steps should follow:

  • Write a script that retrieves comments from all source files using regex (pay attention to also block comments /**/)
  • Purchase (shouldn't be expensive) or get (if you can) an API key for Google Translate: https://cloud.google.com/translate/
  • Once you're able to translate things using a curl requests (RESTful), iterate through all the collected comments and replace each comment with its translated content
  • Finally convert all source files to UTF8 and check that nothing broke

Implement tile events

Description

Implement tile events, where a user walks in specific area and it says this area is safe.

Implement stackable HP/MP bars in state-bar

Description

The 1264 UIFs introduced stackable HP/MP bars in state-bar.

This also requires server and client side implementation for N3PKT_ZONEABILITY (0x5E) with its sub-opcode 2.

We now have 3 different bars stacked on each other that need to change visibility based on the current state of the user.
The state can be either dot (bleeding red), poison (purple) and speed (green).

The server currently does not have implementation for it, hence it's needed to add calls where applicable to send such information so that the client will update the stackable bars depending on the state.

Tasks

  • [client] Load the relevant UI elements (lasting, slow, drop) and hide their visibility
  • [client] Ensure when we update hp (CUIStateBar::UpdateHP) that they also get updated
  • [client] Implement a call in CUIStateBar::Tick to call CUIStateBar::TickAffectingHP every frame where depending on the state it will toggle the stackable bars visibility
  • [client] Allow the client to receive packet to update status via N3PKT_ZONEABILITY packet (CUIStateBar::MsgRecv_UserStatusChange)
  • [server] Change the state of the user depending on the magic attack type and send the state via N3PKT_ZONEABILITY

Crash when trying to create a new character

Description

Character Create Gameprocedure and character create UI.
I'm not sure where it goes wrong, when it reads the tbl it explodes. I can jump out of it and proceed and select warrior then it will say erace barbarian but the stats don't get displayed thus tbl doesnt get read correctly. I'm sure its missing data types.
Openko has had it too, but i can't even get that far.
srmeier/KnightOnline#122

Screenshots

https://cdn.discordapp.com/attachments/388230610012864512/998612212493078718/Knight_Online_2022.07.18_-_17.27.36.01.mp4

Files

tbl: NewChrValue.tbl
client: https://www.mediafire.com/file/ukkvu69gxee1bh5/client_bin.rar/file

To Reproduce

If this issue is describing a bug, include some steps to reproduce the behavior.

Tasks

Include specific tasks in the order they need to be done in. Include links to specific lines of code where the task should happen at.

  • DataTypes
  • Maybe server
  • IDK maybe i took too much on my plate to chew we can return back to older data types and older chrvalue.tbl ..... You can pick

Looting item bundle causes the game to crash

Description

Looting item bundle causes the game to crash.

Official behavior always provide at least one item, which is coins.

To Reproduce

  1. Kill a monster
  2. Try to loot item

Client will crash.

Tasks

  • Check whether packet receive or send works correctly, if yes, enforce the default first item to always be coins.

Implement cape / mantle UIFs

Description

Implement cape / mantle UIFs.

Tasks

  • Implement mantle UIF
  • Make it load custom icons when setting capes

Please Help

hello, how can I destroy the lines in the game or can there be a missing program

Client Uptade Server ini Register Button Active

GameProcLogin.cpp
120 - Add Codes

	char szRegistrationSite[_MAX_PATH];
	memset(szRegistrationSite, 0, sizeof(szRegistrationSite));

	GetPrivateProfileString("Join", "Registration site", "", szRegistrationSite, _MAX_PATH, szIniPath);
	m_szRegistrationSite = std::string(szRegistrationSite);

124 - Until Codes

GameProcLogin.h
19 - Add Codes

	std::string		m_szRegistrationSite;

UILogin.cpp
72 - Add Codes

else if (pSender == m_pBtn_Join)
		{
			if (!CGameProcedure::s_pProcLogIn->m_szRegistrationSite.empty())
			{
				ShellExecute(NULL, "open", CGameProcedure::s_pProcLogIn->m_szRegistrationSite.c_str(), NULL, NULL, SW_SHOWNORMAL);
			}

			return true;
		}

80 - Until Codes

Thanks Knight Online His Family

Fix CN3UIEdit caret position

Description

When setting focus between different CN3UIEdit that has text, then caret position is not properly calculated to put at the end of the text. This causes the caret to go to the beginning.

Implementation related:

void CN3UIEdit::SetCaretPos(UINT nPos)
{
	if (nPos > m_iMaxStrLen) nPos = m_iMaxStrLen;	// 최대 길이보다 길경우 작게 세팅
	m_nCaretPos = nPos;

	const std::string& szBuff = m_pBuffOutRef->GetString();
	__ASSERT(szBuff.empty() || -1 == szBuff.find('\n'), "multiline edit");	// 지금은 multiline은 지원하지 않는다.
	SIZE size = {0,0};
	if (!szBuff.empty() && m_pBuffOutRef ) m_pBuffOutRef->GetTextExtent(szBuff, m_nCaretPos, &size) ;

	int iRegionWidth = m_rcRegion.right - m_rcRegion.left;
	if (size.cx > iRegionWidth) size.cx = iRegionWidth;
	s_Caret.SetPos(m_pBuffOutRef->m_ptDrawPos.x + size.cx, m_pBuffOutRef->m_ptDrawPos.y);
}

Screenshots

image

To Reproduce

  1. Write some text in login screen
  2. Toggle to password using TAB
  3. Toggle back to ID and you'll see the caret is at 0 position.

Manage packet opcodes in a single header file

Description

When searching for "packet" in this url: https://github.com/ko4life-net/ko/find/master

It shows a bunch of files that store the same packet opcodes that the server files, as well as the client communicating with each other.

This is difficult to maintain, as well as doesn't scale nicely. It also makes it difficult to find things in the codebase, such as references to a specific opcode.

Tasks

This task requires some patience and being pedantic. There are also some places when RECV or SEND check for opcodes that don't use proper defines, rather constant values. I would imagine to tackle it, the following tasks should follow:

  • Combine all packet opcodes into a single header file that will be shared with all projects (we can temporarily put it into N3Base)
  • Make all projects use that packet opcodes file
  • Search for instances where constants values are hardcoded, rather than using actual opcode's defines
  • Ensure that all packet opcode defines are prefixed with N3PKT_*

Write cli exporter and importers of game tbls to json format

Description

Write a cli tool that converts game tbls to json files and back.

This is so that when we editing content in the game Data folder, we saw what was changes and also so that we don't need to use a tbl editor.

This tool will take the list of json files and produce tbls in the same encoding format the client lodas.

Tasks

  • Write cli tool that can export and import game tbls to json format
  • Produce json files from game tbls and put it under version control in ko-assets

Load warp list from warpinfo.tbl

Description

Note that warp list information is loaded from SMDs. This isn't great and makes editing difficult, especially because it's in binary format.

Tasks

  • Add warpinfo.tbl to ko-assets where its content matching the ones with our existing SMDs
  • Load warpinfo.tbl in the client
  • Implement listing warp info based on what is in the tbl
  • Remove from the server sending over warp info to reduce packet size

Migrate from legacy D3DXMath library to DirectXMath

Description

Note that in order to use D3DXMath library, we need to install additional redists, as well as DirectX-9 June. This is uncessary, since Microsoft ever since Windows 8 includes in the system the replacement for it known as DirectXMath:

The legacy D3DXMath library has been replaced by DirectXMath. The library is in the Windows 8.x SDK or later, as well as being available on GitHub. You may also want to take a look at the SimpleMath wrapper.

More info: https://walbourn.github.io/living-without-d3dx/

Implement in game zone's flags

Description

Implement in game zone's flags.

In later versions Zones.tbl, there is *.flag file that we need to load and based on the data there, render it in game.

Tasks

  • Implement in game zone's flags

Cleanup compile warnings

Description

Updating compiler means more things are being properly detected as either bad practice or wrong usage.

We enable warning level 3 and with the current codebase the amount of warnings we get is super spammy that one might miss the actual important warnings.
Therefore some specific spammy warnings were muted as can be seen in the following snippet:
https://github.com/stevewgr/ko/blob/7c09c9ce8a5042bbeceb4af9a6987279365a2542/src/game/KnightOnLine.vcxproj#L64

To Reproduce

Remove the warnings one by one and start compiling. You'll see lot of warnings showing up.

Tasks

This will change things quite a bit in code, therefore let's separate each removal of muted warnings into separate tasks / PRs:

  • /wd4018
  • /wd4091
  • /wd4244
  • /wd4267
  • /wd4477
  • /wd4838
  • /wd6031
  • /wd2649

Do note that this was done for all projects. In order to really address all usages, make sure to remove from each project. This can be done with a simple find and replace regex pattern.

Camera zooming itself when right clicking

Description

When right clicking in game, the game camera changing its position as if it's zooming by itself.

To Reproduce

Run the game and right click in order to rotate the camera.

Tasks

  • Fix game camera rotation control when right clicking

client data files missing

Hello,
First of all thanks for your effort project looks pretty good!
Can you share client Data/Object/UI etc. Folders?

Implement command list UIF

Description

Implement command list UIF

This is where we can seek party, greet, town, and the list goes on.

Tasks

  • Show command list when H key is pressed
  • Add all if not most commands boiler plate code, so if can't be implemented now, then it will be later

Support newer FX formats

Description

Even though we keep the client old school, there are some nice effects and in HDR quality in newer clients that would be nice adapting into the old version.

Tasks

  • Support newer FX Bundle formats (28 instead of 16)
  • Support newer FX Parts formats (at least just loading them for now)
  • Replace old fx directory from latest version in the 1097 client
  • Note that also this task needs to be done for this to work: #86

When exiting the game, character data is not being saved

Description

When exiting the game, character data is not being saved.

This could be either server side code issue or most likely a stored procedure.

To Reproduce

  1. Create character
  2. Level up
  3. Exit the game
  4. Login back

You'll start over.

Tasks

  • Fix character saving issue when exiting the game

Upgrade UIs to 1264 so that the client will be in English

Description

Currently the UIs are in Korean.

We need to make the old school 1097 client UIs support English.

Note that the 1264 client UIs is the first client to be in English.

Tasks

  • Update ko-assets and remove all UIs and then replace them with the UIs from 1264 client
  • Make the client sources loading the UIs.tbl from 1264 client
  • Update the sources to load the new UIs

Implement Zone Flags (*.flag)

Description

The client still doesn't know about FLAG file format in order to load flags.

Tasks

  • Load FLAG file format
  • Hook it into game procedure to tick and render it after CN3ShapeMgr.

Countable items doesn't have serial

Description

Countable items doesn't have serial so if someones find way to duplicate items i it will be undetectable. We need to secure that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.