132 Commits

Author SHA1 Message Date
Túlio Henrique
50180b594d Added preprocessor directive on mobilefacade.cpp 2015-08-16 13:54:46 -03:00
Túlio Henrique
2fbd29ff8b Added preprocessor directive on mobilefacade.cpp 2015-08-16 13:48:35 -03:00
Túlio Henrique
7cb1c9fae8 Merge branch 'mobile_port' of https://github.com/edubart/otclient into mobile_port 2015-08-16 13:40:30 -03:00
Túlio Henrique
0deeabbfda Removed SDL2 and created JNI interface (Android Only) 2015-08-16 13:39:27 -03:00
Túlio Henrique
01992aae7e Merge branch 'master' of https://github.com/edubart/otclient into mobile_port 2015-08-13 10:05:00 -03:00
Túlio Henrique
90ff929291 Update README.md 2015-08-05 09:14:33 -03:00
Túlio Henrique
0e87c8355b Improved input handler 2015-07-28 07:55:27 -03:00
Konrad Kuśnierz
ae25dbf6a5 Merge pull request #678 from ranisalt/fix-deprecated-openssl
Replace deprecated function
2015-07-25 02:37:50 -05:00
Ranieri Althoff
058b926a94 Replace deprecated function 2015-07-24 20:52:31 -03:00
Konrad Kuśnierz
4591a37844 My bad, wrong function name. 2015-07-19 10:33:55 +02:00
Konrad Kuśnierz
8abefb1505 Fix #601, fix #599 2015-07-19 10:27:06 +02:00
BenDol
0afbfd58ce Proper gameinterface load sequence, thanks @Quintinon
https://github.com/edubart/otclient/pull/677
2015-07-19 07:08:21 +12:00
BenDol
6c5549dd46 This was already fixed. 2015-07-19 06:48:05 +12:00
Ben Dol
0e0da9ecbf Merge pull request #654 from crackcomm/market-myoffers
Market myoffers + NPC start block
2015-07-19 06:46:33 +12:00
BenDol
7a7f63586f Fix serverlist issues from previous commit.
Was referencing a 'global' variable so a simple check will do.
2015-07-19 06:34:21 +12:00
BenDol
07a2995285 Fix NPC static text and missing SpeakType. 2015-07-19 06:03:19 +12:00
TheSumm
b822e92c0e Fixed serverlist to ignore invalid settings when loading 2015-07-17 11:22:31 +02:00
Henrique Santiago
0ecf2229e6 Merge pull request #674 from kenfal/master
Fix missing loop counter, fixes #673
2015-07-10 21:45:48 -03:00
kenfal
47272519b5 Fix missing loop counter 2015-07-10 21:41:53 -03:00
Túlio Henrique
57b9ad88eb Added 'strings.xml' on android project 2015-07-07 10:52:05 -03:00
Túlio Henrique
2d65a0a3ed Merge with 'master' 2015-07-07 10:43:18 -03:00
Túlio Henrique
b18c60eb77 Added .bat file to compile on windows 2015-07-07 10:35:09 -03:00
Konrad Kuśnierz
481adcdc21 Merge pull request #672 from diath/patch-motd
Fix last motd number saving
2015-07-06 09:53:50 +02:00
Kamil Chojnowski
78bdf20603 Fix last motd number saving 2015-07-06 07:38:01 +02:00
Sam
0642fb66cd Merge pull request #671 from ottools/master
Fix #664
2015-07-05 18:34:54 +02:00
Nailson
5ef55307f5 Fix #664 2015-07-05 13:05:12 -03:00
Konrad Kuśnierz
b9848f360c Check for Otc::GameAttackSeq feature 2015-06-21 12:44:19 +02:00
Konrad Kuśnierz
b5a14ddb68 Add context menu option "Select all" for channel 2015-06-04 22:10:05 +02:00
Eduardo Bart
e4302562ff Change new line from CR LF to LF when copying console text 2015-06-04 12:10:32 -03:00
Konrad Kuśnierz
471b8362e2 ConsoleLabel should not be focusable
Avoid scrolling by ensureChildVisible of UIScrollArea
2015-06-04 15:31:15 +02:00
Konrad Kuśnierz
a33fcd19b4 Improve multi-line selection (find bouding children) 2015-06-03 23:00:39 +02:00
Konrad Kuśnierz
7f2f70e1a6 Change signalcall to protectedcall in console
Even though they kind of do the same (calling function in protected
mode), @edubart suggested to use a different function for the sake of
readability.
2015-06-03 17:03:49 +02:00
Konrad Kuśnierz
f9d183837a Add option to save messages from channel 2015-06-03 16:46:49 +02:00
Eduardo Bart
02c6b1b6c7 Missing changes for multiline text 2015-06-03 10:59:28 -03:00
Eduardo Bart
0c1540e531 Improve multiline text selection, closes #507 2015-06-03 10:51:39 -03:00
Konrad Kuśnierz
6893a5e98a Optimize UITextEdit rendering 2015-06-03 14:56:43 +02:00
Konrad Kuśnierz
559e545e36 Few more minor fixes to selection in game console 2015-06-02 22:46:33 +02:00
Konrad Kuśnierz
cf90bb9807 Fix selection
Perhaps it would be wise to move widget local variables to some sort of
global variable for each tab.
2015-06-02 20:04:34 +02:00
Konrad Kuśnierz
f35c939fc3 Start working on multi-line selection for console
Unfortunately UITextEdit is really bad in terms of performance. It
cannot be used as overlying widget (just like in terminal). On the other
hand we could optimize it by rewriting (unfortunately) the whole widget.

There still is a lot of things to do, but for now it is possible to
select several lines of text and copy it using CTRL + C. In order to
make text copyable in context menu it will be required to override
onMousePress (return true).
2015-06-02 19:16:41 +02:00
Konrad Kuśnierz
34e2fa1d49 Merge pull request #665 from ranisalt/master
Use native optimizations instead of hardcoded defaults
2015-05-27 11:12:12 -05:00
Ranieri Althoff
944b220c90 Use native optimizations instead of hardcoded defaults 2015-05-25 00:59:39 -03:00
Konrad Kuśnierz
c5ea8c98fb Add cn option to struct
"cn" a sequence of exactly n chars corresponding to a single lua string.
2015-05-19 18:50:02 +02:00
Konrad Kuśnierz
02ab50d8dd Minor mistake in unpacking string 2015-05-19 13:41:40 +02:00
Konrad Kuśnierz
48fefb03cb Add float and double support for struct
@edubart suggested it would be still better to have it done within C as
extra module (just like lbitlib).

"f" a float (4 bytes).
"d" a double (8 bytes).

http://en.wikipedia.org/wiki/IEEE_floating_point
2015-05-19 13:13:14 +02:00
Konrad Kuśnierz
7ea6c46b2c Add binary operations for lua
This is something I was always missing - posibbility to operate on
binary files or streams in pure lua. In most cases we do only need to
read simple variables from files such as integers with different amount
of bytes. This "class" will provide that ability.

It's a simple implementation of following C module for lua:
http://www.inf.puc-rio.br/~roberto/struct/

It has much less, though. Following elements have been implemented:

">" flag to set mode to big endian.
"<" flag to set mode to little endian.
"b" a signed char.
"B" an unsigned char.
"h" a signed short (2 bytes).
"H" an unsigned short (2 bytes).
"i" a signed int (4 bytes).
"I" an unsigned int (4 bytes).
"l" a signed long (8 bytes).
"L" an unsigned long (8 bytes).
"s" a zero-terminated string.

An example how to use it:

```lua
local packed = Struct.pack('<LIhBsb', 123456789123456789, 123456789,
-3200, 255, 'Test message', -1)
-- packed is now a lua string we can save to file as binary data
local L, I, h, B, s, b = Struct.unpack('<LIhBsb', packed)
print(L, I, h, B, s, b)
```

You can use g_resources.readFileContents as function to read binary
files and parse them via this class.
2015-05-18 21:38:05 +02:00
Konrad Kuśnierz
0597ded1d3 Merge pull request #648 from Shawak/Shawak-b_vc12
fix visual studio include paths
2015-05-13 06:32:47 -05:00
Konrad Kuśnierz
c3c2ac80e7 Fix drawing creatures in UICreature
I will leave this ugly hack for the time being, but I do encourage to change it later on. The whole "resize" boolean makes no sense since the outfit is resized by the destination rectangle anyway. I believe we should give it a try with a real size of the object defined in dat by the user for creatures bigger than 32x32.

Please keep in mind that we did cut bigger creatures to 48x48 (2*Otc::TILE_PIXELS*0.75f) before as well, so nothing really changed besides ability to properly draw bigger creatures than 64x64 on battlelist.
2015-05-12 23:44:10 +02:00
Konrad Kuśnierz
6bd0e37670 Correctly draw creatures bigger than 64x64
Battlelist icon
2015-05-12 10:16:14 +02:00
Konrad Kuśnierz
11990815a6 Correctly load corrupted otmm file, should fix #606 2015-05-10 22:31:10 +02:00
Konrad Kuśnierz
ded8fef16b Change bitter badge and made it inline with travis 2015-05-10 22:25:46 +02:00
Konrad Kuśnierz
53dbbd2ba3 Decrease RAM usage by at least 200MB
This was quite ridiculous.

TILESTATE_LAST = 1 << 24

Basically we were creating 2^24 Color structures within the array, each
of them has 4 floats (16 bytes) resulting in about 256 MB of extra
wasted memory.
2015-05-09 20:27:04 +02:00
Eduardo Bart
e4cdb3834b Merge pull request #661 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2015-05-08 11:28:17 -03:00
The Gitter Badger
86dd7958e1 Added Gitter badge 2015-05-07 21:42:02 +00:00
Ben Dol
d0a365144e Added 'Compiling for Android' wiki page 2015-04-24 15:44:55 +12:00
Ben Dol
d88505bf8d Temporarily change the travis branch to 'mobile_port' will change this back later. 2015-04-24 15:25:20 +12:00
BenDol
789f86a778 Merge branch 'master' of https://github.com/Tulioh/otclient into mobile_port 2015-04-24 15:16:28 +12:00
Túlio Henrique
eecf8beb2f Update README.md 2015-04-23 21:20:53 -03:00
Túlio Henrique
121e6b29ef Update README.md 2015-04-23 20:53:03 -03:00
Túlio Henrique
c2b25abd37 Update README.md 2015-04-23 20:52:46 -03:00
Túlio Henrique
6016c87337 Update README.md 2015-04-23 20:51:45 -03:00
Tulioh
11a81650e4 Added SDLActivity on project folder 2015-04-23 14:45:12 -03:00
Tulioh
44ddbc34e8 Fix on android finger 2015-04-23 14:40:24 -03:00
Tulioh
4605c72546 Fix on android finger 2015-04-23 14:40:18 -03:00
TheSumm
fe98efdc21 Fix modal dialog auto sizing, fixes #556 2015-04-20 19:22:50 +02:00
TheSumm
8e5bbcd3a1 Add tab-spacing tag to MoveableTabBars 2015-04-20 03:32:32 +02:00
Tulioh
2b96ae7f09 Merge https://github.com/edubart/otclient 2015-04-19 12:22:16 -03:00
TheSumm
f936ab9aab fix #638 2015-04-19 13:59:45 +02:00
TheSumm
84f6cdec86 Fix #576, PopupMenus close when leaving the game 2015-04-19 13:56:03 +02:00
TheSumm
ab5bed456b Fix warning 2015-04-19 13:54:55 +02:00
Łukasz Kurowski
01c107ba62 Market my offers 2015-04-07 01:24:39 +02:00
Łukasz Kurowski
ff0947c270 NPC start talk 2015-03-31 10:37:49 +02:00
Shawak
a3e6cc54b5 fix visual studio include paths 2015-03-22 20:11:41 +01:00
TheSumm
fcd481ee15 Added missing message mode 2015-03-09 23:26:39 +01:00
TheSumm
b237b713ef Fix 10.76 login protocol, added missing lua consts 2015-03-09 16:46:26 +01:00
TheSumm
3bffa6b04a Terminal new line (Shift+Enter) functionality 2015-03-07 16:32:45 +01:00
TheSumm
83dc129f03 Protocol 10.76, fixed death window & death packet 2015-03-07 06:10:10 +01:00
Konrad Kuśnierz
ca60efd786 Merge pull request #645 from gpedro/master
Update copyright for 2015
2015-03-04 16:43:38 +01:00
Gabriel Pedro
04b516a1a0 Update copyright for 2015 2015-03-04 10:36:51 -04:00
Eduardo Bart
5387f8fe83 Merge pull request #632 from SuggestName/master
Minor change in statusLabel anchor for Ctrl + .
2015-02-23 11:49:05 -03:00
Suggest Name
b5c7374890 Update textmessage.otui 2015-02-21 20:40:16 -02:00
TheSumm
f51a160bde Remove unintentionally added files 2015-02-15 03:27:32 +01:00
TheSumm
cbf70c1d63 Enable protocol 10.75 2015-02-15 03:25:43 +01:00
Henrique Santiago
28ff65be5a Merge pull request #640 from Mignari/master
Added support for enhanced animations for items.
2015-02-13 16:07:07 -02:00
Nailson
74af47f4d6 Added support for enhanced animations for items.
Thanks to @conde2, @BenDol
2015-02-13 08:19:45 -03:00
TheSumm
4c4e0b9d07 Fix error showing after relogging with containers being open 2015-02-12 14:48:56 +01:00
TheSumm
6961492e00 Fix console tabs not blinking, closes #627 2015-01-30 19:56:56 +01:00
TheSumm
1c3cfddab0 Removed tr() from UIHeader 2015-01-28 00:01:53 +01:00
TheSumm
71931b961a Full protocol 10.74 support (session key), entergame style fixes 2015-01-27 23:44:37 +01:00
TheSumm
64e9406488 Fixed 'widget destroyed but still have 1 reference left' related to console module 2015-01-27 21:14:38 +01:00
BenDol
900ebbd985 Fixes #181 and fixes #551 2015-01-27 22:11:52 +13:00
TheSumm
cb7cea6809 Tiny signalcall fix 2015-01-25 21:20:48 +01:00
TheSumm
4e2ded571e Fixed not being able to relog after reloading gamelib 2015-01-25 14:17:16 +01:00
TheSumm
da2762dac3 Market now highlights offers which differ from the average price 2015-01-25 13:41:00 +01:00
TheSumm
eb3c244023 More Market fixes
* Fixed NextButton and PreviousButton style
* Little update to SpinBox including dontSignal
* Market cleanup and more check to be safe
* Limit Market amountWindow by player balance
* Set proper maximum amount when creating offers
* Fixed fee label not calculating the correct fee sometimes
2015-01-25 02:28:53 +01:00
TheSumm
3157e7924f Market updates, now using showAs / tradeAs so every items works properly, some cleanup / bug fixing 2015-01-23 02:52:05 +01:00
TheSumm
92e2e8224f Added market message, reworked text messages a little 2015-01-22 20:38:28 +01:00
TheSumm
1d022905ab Fix button style (closes #607) 2015-01-22 11:49:04 +01:00
TheSumm
607dab01d6 Added Market column sorting (fixes #429), updated UITable and fixed not working methods 2015-01-21 23:40:15 +01:00
TheSumm
6edc73a8ba Fix Enter Game window not being centered on startup 2015-01-21 18:58:30 +01:00
TheSumm
596717bf32 Added locale number formatting to locale files 2015-01-20 16:18:04 +01:00
TheSumm
b5cea41f87 Market fixes
- Market now works after reloading
- Fixed button / tab styles
- MarketRightTabBarButton now inherits the base style
- Fixed amountWindow buttons being hardly clickable
- "All" search filter is now on by default
2015-01-20 14:34:45 +01:00
TheSumm
8542f8bfd4 Protocol 10.73 support 2015-01-20 11:07:38 +01:00
TheSumm
fc76ca4523 Updated advernturer blessing inventory style to be more robust 2015-01-19 01:52:49 +01:00
TheSumm
63f95317a2 Fixed baseSpeed not being parsed (10.59+) 2015-01-19 01:08:18 +01:00
TheSumm
26fb35fd4d Fixed major bug 2015-01-18 23:57:19 +01:00
TheSumm
a8f2bb19db Little polishing of Unjustified Points module 2015-01-18 23:56:44 +01:00
TheSumm
ddec9627b8 Protocol 10.72 (Authenticator) Support, Unjustified Points diplay
- Unjustified Points (Better topbar icon would be nice)
![Unjustified Points](http://i.gyazo.com/81286f46d9b4d56b3fe864140173cf34.png)
- Authenticator token support
- adjusted 'can change pvp frame' to 1054
- ...
2015-01-18 15:14:07 +01:00
TheSumm
24b1526534 Fixed sending wrong OS 2015-01-11 18:50:58 +01:00
Tulioh
fcf545133b Merge branch 'master' of https://github.com/Tulioh/otclient 2015-01-11 13:25:29 -02:00
Tulioh
fd97ccd402 Implemented some event handlers in SDL 2015-01-11 13:24:54 -02:00
Konrad
bf30fc0dc3 Add QEZC for diagonal walking as well as broadcast/red talk for gamemasters 2015-01-06 18:23:36 +01:00
Túlio Henrique
4807c4a19d Removed the line that delete the build folder 2015-01-02 07:31:58 -02:00
Tulioh
15b3d439d6 Compiling and running on android, but the window is black when run (bug) 2014-12-30 23:36:42 -02:00
TheSumm
4b7770361d Fix parseLogin packet 2014-12-30 19:25:20 +01:00
TheSumm
50c36bb2ba Fix for adventurer blessing style 2014-12-30 16:40:20 +01:00
BenDol
16f6a0019c Fix dat loading issue with 10.00 & minor outfit window fix. 2014-12-30 17:27:53 +13:00
TheSumm
7f3f18f991 Support for Protocols up to 10.71, Adventurer Blessing 2014-12-29 18:08:33 +01:00
Ben Dol
6ab69b499d Merge pull request #567 from cymruu/master
A tool which generates empty locales template
2014-12-26 23:54:42 -08:00
Konrad Kuśnierz
f724506550 Merge pull request #614 from gpedro/patch-1
Add OSX Compiling Wiki
2014-12-26 21:35:22 +01:00
Tulioh
389c7f2a60 Compiling for android but have some bugs 2014-12-25 14:22:37 -02:00
Tulioh
c28d2c1555 Compiling for android but have some bugs 2014-12-25 14:20:38 -02:00
Gabriel Pedro
1eb2bbd389 Add OSX Wiki 2014-12-25 11:10:03 -03:00
Tulioh
7e34c452a1 Added some files to this commit 2014-12-22 00:37:07 -02:00
Tulioh
b3b314f01b Android compilation added. 2014-12-19 00:56:55 -02:00
Tulioh
997daa2d49 Starting to create CMakeLists to android 2014-12-18 00:31:28 -02:00
Konrad Kusnierz
5ada7eb5ec Fix #600 2014-11-20 21:14:25 +01:00
Konrad Kusnierz
c49a6f3bf2 Fix for #596 2014-11-19 07:25:36 +01:00
Eduardo Bart
aa924dc348 Fixes in CMake for building snapshots 2014-11-05 11:25:11 -02:00
Eduardo Bart
f6fb785cea Use -O2 in release build 2014-11-05 10:45:28 -02:00
BenDol
25e7b1d7a3 Fix logging in with 760 (until a better solution is found). 2014-11-04 13:27:21 +13:00
BenDol
bdfb77166e Reuse code by merging dash functionality with walk method. 2014-11-03 15:12:14 +13:00
cymruu
ac23b8e624 made a tool which generates empty locales template 2014-09-15 19:43:05 +02:00
428 changed files with 6749 additions and 1264 deletions

14
.gitignore vendored
View File

@@ -4,12 +4,26 @@ CMakeFiles
cmake_install.cmake cmake_install.cmake
Makefile Makefile
/otclient /otclient
/android/project/build.xml
/android/project/proguard-project.txt
/android/project/gen
/android/project/bin
/android/project/libs
/android/project/.settings
/android/project/.classpath
/android/project/.project
/android/project/.cproject
/android/project/local.properties
/android/project/project.properties
libs*
.idea*
/*.h /*.h
/*.cxx /*.cxx
*.o *.o
*.gch *.gch
*.a *.a
*.exe *.exe
*.so
*.spr *.spr
*.dat *.dat
*.kdev* *.kdev*

View File

@@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 2.6)
project(otclient) project(otclient)
set(VERSION "0.6.6") set(VERSION "0.6.6")
set(LIB_NAME "otc_framework")
option(FRAMEWORK_SOUND "Use SOUND " ON) option(FRAMEWORK_SOUND "Use SOUND " ON)
option(FRAMEWORK_GRAPHICS "Use GRAPHICS " ON) option(FRAMEWORK_GRAPHICS "Use GRAPHICS " ON)
@@ -20,10 +19,6 @@ endif()
option(USE_PCH "Use precompiled header (speed up compile)" OFF) option(USE_PCH "Use precompiled header (speed up compile)" OFF)
set(executable_SOURCES
src/main.cpp
)
# add executable icon for win32 platforms # add executable icon for win32 platforms
if(WIN32) if(WIN32)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/otcicon.o add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/otcicon.o
@@ -36,20 +31,19 @@ endif()
add_definitions(-D"VERSION=\\"${VERSION}\\"") add_definitions(-D"VERSION=\\"${VERSION}\\"")
# we want framework to be a library for faster compilation/linking set(executable_SOURCES
if(USE_STATIC_LIBS) src/main.cpp
add_library(${LIB_NAME} ${framework_SOURCES}) )
if(ANDROID)
# add shared library for android
add_library(${PROJECT_NAME} SHARED ${framework_SOURCES} ${client_SOURCES} ${executable_SOURCES})
else() else()
add_library(${LIB_NAME} SHARED ${framework_SOURCES}) # add client executable
message(STATUS "Linking to shared ${LIB_NAME}, make sure you copy the DLL/SO/dylib with the executable!") add_executable(${PROJECT_NAME} ${framework_SOURCES} ${client_SOURCES} ${executable_SOURCES})
endif() endif()
target_link_libraries(${LIB_NAME} ${framework_LIBRARIES})
# add client executable target_link_libraries(${PROJECT_NAME} ${framework_LIBRARIES})
add_executable(${PROJECT_NAME} ${client_SOURCES} ${executable_SOURCES})
# target link libraries
target_link_libraries(${PROJECT_NAME} ${LIB_NAME})
if(USE_PCH) if(USE_PCH)
include(cotire) include(cotire)
@@ -61,7 +55,7 @@ endif()
# installation # installation
set(DATA_INSTALL_DIR share/${PROJECT_NAME}) set(DATA_INSTALL_DIR share/${PROJECT_NAME})
install(TARGETS ${PROJECT_NAME} ${LIB_NAME} install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib) ARCHIVE DESTINATION lib)

View File

@@ -1,6 +1,6 @@
OTClient is made available under the MIT License OTClient is made available under the MIT License
Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient> Copyright (c) 2010-2015 OTClient <https://github.com/edubart/otclient>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
[![Build Status](https://secure.travis-ci.org/edubart/otclient.svg?branch=master)](http://travis-ci.org/edubart/otclient) [![Build Status](https://secure.travis-ci.org/edubart/otclient.svg?branch=mobile_port)](http://travis-ci.org/edubart/otclient) [![Join the chat at https://gitter.im/edubart/otclient](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/edubart/otclient?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
### What is otclient? ### What is otclient?
Otclient is an alternative Tibia client for usage with otserv. It aims to be complete and flexible, Otclient is an alternative Tibia client for usage with otserv. It aims to be complete and flexible,
@@ -8,6 +8,17 @@ that each functionality is a separated module, giving the possibility to users m
anything easily. Users can also create new mods and extend game interface for their own purposes. anything easily. Users can also create new mods and extend game interface for their own purposes.
Otclient is written in C++2011, the upcoming C++ standard and heavily scripted in lua. Otclient is written in C++2011, the upcoming C++ standard and heavily scripted in lua.
## The Mobile Project
This is a fork of edubart's otclient. The objective of this fork it's to develop a runnable otclient on mobiles devices.
Tasks that need to do:
- [X] Compile on Android devices
- [ ] Compile on Apple devices
- [ ] Adapt the UI reusing the existing lua code
Current compiling tutorials:
* [Compiling for Android](https://github.com/edubart/otclient/wiki/Compiling-for-Android)
### Where do I download? ### Where do I download?
The latest commits compiled for Windows can be found here. The latest commits compiled for Windows can be found here.
@@ -41,7 +52,7 @@ A package with all required libraries for compiling OTClient on Windows can be f
In short, if you need to compile OTClient, follow these tutorials: In short, if you need to compile OTClient, follow these tutorials:
* [Compiling on Windows](https://github.com/edubart/otclient/wiki/Compiling-on-Windows) * [Compiling on Windows](https://github.com/edubart/otclient/wiki/Compiling-on-Windows)
* [Compiling on Linux](https://github.com/edubart/otclient/wiki/Compiling-on-Linux) * [Compiling on Linux](https://github.com/edubart/otclient/wiki/Compiling-on-Linux)
* [Compiling on OS X](https://github.com/edubart/otclient/wiki/Compiling-on-Mac-OS-X)
### Need help? ### Need help?

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
#!/bin/sh
mkdir -p ../build_android && cd ../build
cmake -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DANDROID_NATIVE_API_LEVEL=android-16 -DANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.6 ..
make
cd ../
cp -r libs android/project/
cp $ANDROID_NDK/libraries/lib/libSDL2.so android/project/libs/armeabi-v7a
cd android/project
android update project -p . --name OTClientMob --target android-16
ant debug
cd bin
adb install -r OTClientMob-debug.apk

View File

@@ -0,0 +1,18 @@
cd ..
mkdir build_android
cd build_android
cmake -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DANDROID_NATIVE_API_LEVEL=android-16 -DANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.6 ..
make
cd ../
xcopy /E /Y libs android\project\libs
cd android\project
call android update project -p . --name OTClient --target android-16
call ant debug
cd bin
adb install -r OTClient-debug.apk

View File

@@ -0,0 +1,21 @@
cd ..
mkdir build_android
cd build_android
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DANDROID_NATIVE_API_LEVEL=android-16 -DANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.6 ..
make
cd ../
ECHO D|xcopy /E /Y android\project\jni\libSDL2.so android\project\libs\armeabi-v7a
xcopy /E /Y libs\armeabi-v7a\libotclient.so android\project\jni
cd android\project
call android update project -p . --name OTClient --target android-16
call ndk-build.cmd all NDK_DEBUG=1
call ant clean
call ant debug
cd bin
adb install -r OTClient-debug.apk

View File

@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.otclient.mobile" android:versionCode="1" android:versionName="1.0">
<application
android:debuggable="true"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:hardwareAccelerated="true" >
<activity android:name="MainActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="otclient"/>
</activity>
</application>
<!-- Android 2.3.3 -->
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="16"/>
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000"/>
<!-- Allow writing to external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Allow make internet connections -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -0,0 +1,6 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libotclient
LOCAL_SRC_FILES := libotclient.so
include $(PREBUILT_SHARED_LIBRARY)

View File

@@ -0,0 +1,2 @@
APP_PLATFORM := android-9
APP_ABI := armeabi-v7a

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">OTClient</string>
</resources>

View File

@@ -0,0 +1,52 @@
package com.otclient.mobile;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
public class FakeEditText extends View implements View.OnKeyListener {
InputConnection ic;
public FakeEditText() {
super(MainActivity.getInstance());
setFocusableInTouchMode(true);
setFocusable(true);
setOnKeyListener(this);
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override // This handles the hardware keyboard input
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.isPrintingKey()) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
}
return true;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
onNativeKeyDown(keyCode);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
onNativeKeyUp(keyCode);
return true;
}
return false;
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
ic = new InputConnectionForNative(this, true);
return ic;
}
public static native void onNativeKeyDown(int keyCode);
public static native void onNativeKeyUp(int keyCode);
}

View File

@@ -0,0 +1,47 @@
package com.otclient.mobile;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
public class InputConnectionForNative extends BaseInputConnection {
public InputConnectionForNative(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
}
@Override // This handles the keycodes from soft keyboard
public boolean sendKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (event.isPrintingKey()) {
commitText(String.valueOf((char) event.getUnicodeChar()), 1);
}
FakeEditText.onNativeKeyDown(keyCode);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
FakeEditText.onNativeKeyUp(keyCode);
return true;
}
return super.sendKeyEvent(event);
}
@Override // Typed text
public boolean commitText(CharSequence text, int newCursorPosition) {
nativeCommitText(text.toString(), newCursorPosition);
return super.commitText(text, newCursorPosition);
}
@Override // Workaround to capture backspace key
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
if (beforeLength == 1 && afterLength == 0) {
return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
&& super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
public static native void nativeCommitText(String text, int newCursorPosition);
}

View File

@@ -0,0 +1,33 @@
package com.otclient.mobile;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
public class KeyboardSoftHandler{
private View editText;
public KeyboardSoftHandler() {
editText = new FakeEditText();
MainActivity.getInstance()
.addViewToLayout(editText);
}
public void showKeyboardSoft() {
editText.setVisibility(View.VISIBLE);
editText.requestFocus();
InputMethodManager imm = (InputMethodManager) MainActivity
.getInstance().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(editText, 0);
}
public void hideKeyboardSoft() {
editText.setVisibility(View.GONE);
InputMethodManager imm = (InputMethodManager) MainActivity
.getInstance().getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
}

View File

@@ -0,0 +1,110 @@
package com.otclient.mobile;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
public class MainActivity extends Activity {
public static final String APP_TAG = "OTClientMob";
private static MainActivity instance;
private RelativeLayout layout;
private static boolean started;
static {
started = false;
//android.os.Debug.waitForDebugger();
System.loadLibrary("otclient");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.v(APP_TAG, "onCreate()");
super.onCreate(savedInstanceState);
initialize();
}
private void initialize() {
instance = this;
layout = new RelativeLayout(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layout.setLayoutParams(params);
setContentView(layout);
if( !started ) {
nativeInit();
started = true;
}
NativeFacadeCalls.initialize();
}
@Override
protected void onPause() {
Log.v(APP_TAG, "onPause()");
super.onPause();
nativePause();
}
@Override
protected void onResume() {
Log.v(APP_TAG, "onResume()");
super.onResume();
if( NativeFacadeCalls.isSurfaceReady() )
nativeResume();
}
@Override
protected void onDestroy() {
Log.v(APP_TAG, "onDestroy()");
super.onDestroy();
if(isFinishing()) {
NativeFacadeCalls.destroy();
destroy();
nativeDestroy();
}
}
private void destroy() {
instance = null;
layout = null;
}
@Override // Ignore certain special keys so they're handled by Android
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
) {
return false;
}
return super.dispatchKeyEvent(event);
}
public static MainActivity getInstance() {
return instance;
}
public void addViewToLayout(View view) {
layout.addView(view);
}
public native void nativeInit();
public native void nativePause();
public native void nativeResume();
public native void nativeDestroy();
}

View File

@@ -0,0 +1,45 @@
package com.otclient.mobile;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
public class NativeFacadeCalls {
private static NativeSurfaceView nativeSurfaceView;
private static KeyboardSoftHandler keyboardSoftHandler;
private static Handler handler;
public static void initialize() {
handler = new Handler(Looper.getMainLooper());
keyboardSoftHandler = new KeyboardSoftHandler();
nativeSurfaceView = new NativeSurfaceView();
MainActivity.getInstance().addViewToLayout(nativeSurfaceView);
}
public static void destroy() {
handler = null;
nativeSurfaceView = null;
keyboardSoftHandler = null;
}
public static boolean isSurfaceReady() {
return nativeSurfaceView.isSurfaceReady();
}
/*
* Static methods called from JNI
*/
public static Surface getNativeSurface() {
return nativeSurfaceView.getSurface();
}
public static void showKeyboardSoft() {
handler.post(new Runnable() {
@Override
public void run() {
keyboardSoftHandler.showKeyboardSoft();
}
});
}
}

View File

@@ -0,0 +1,38 @@
package com.otclient.mobile;
public class NativeMainThread {
private static final NativeMainThread instance;
private Thread nativeThread;
static {
instance = new NativeMainThread();
}
private NativeMainThread() {}
public void start() {
if( nativeThread == null ) {
nativeThread = new Thread(
new NativeThread(), "NativeThread" );
nativeThread.start();
}
}
public static NativeMainThread getInstance() {
return instance;
}
/*
* Native methods implemented on C++
*/
public native void nativeStartApp();
private class NativeThread implements Runnable {
@Override
public void run() {
nativeStartApp();
}
}
}

View File

@@ -0,0 +1,130 @@
package com.otclient.mobile;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
public class NativeSurfaceView extends SurfaceView implements
SurfaceHolder.Callback, View.OnTouchListener {
private Surface surface;
private GestureDetector gestureDetector;
private int currentWidth;
private int currentHeight;
private boolean surfaceReady;
private final int LONGPRESS_EVENT = 3;
public NativeSurfaceView() {
super(MainActivity.getInstance());
getHolder().addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
setOnTouchListener(this);
currentWidth = 0;
currentHeight = 0;
surfaceReady = false;
gestureDetector = new GestureDetector(
new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent event) {
onNativeTouch(LONGPRESS_EVENT, event.getX(), event.getY());
}
});
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(MainActivity.APP_TAG, "surfaceCreated");
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.d(MainActivity.APP_TAG, "surfaceChanged");
surface = holder.getSurface();
currentWidth = width;
currentHeight = height;
surfaceReady = true;
onNativeResize(width, height);
onNativeSurfaceChanged();
NativeMainThread.getInstance().start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(MainActivity.APP_TAG, "surfaceDestroyed");
surface = null;
surfaceReady = false;
onNativeSurfaceDestroyed();
}
@Override
public boolean onTouch(View view, MotionEvent event) {
gestureDetector.onTouchEvent(event);
/* Ref: http://developer.android.com/training/gestures/multi.html */
final int pointerCount = event.getPointerCount();
int action = event.getActionMasked();
int i = -1;
float x,y;
switch(action) {
case MotionEvent.ACTION_MOVE:
for (i = 0; i < pointerCount; i++) {
x = event.getX(i);
y = event.getY(i);
onNativeTouch(action, x, y);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
// Primary pointer up/down, the index is always zero
i = 0;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN:
// Non primary pointer up/down
if (i == -1) {
i = event.getActionIndex();
}
x = event.getX(i);
y = event.getY(i);
onNativeTouch(action, x, y);
break;
default:
break;
}
return true;
}
public Surface getSurface() {
return surface;
}
public boolean isSurfaceReady() {
return surfaceReady;
}
/*
* Native methods implemented on C++
*/
public native void onNativeSurfaceChanged();
public native void onNativeSurfaceDestroyed();
public native void onNativeResize(int width, int height);
public native void onNativeTouch(int actionType, float x, float y);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -4,6 +4,10 @@ locale = {
charset = "cp1252", charset = "cp1252",
languageName = "Deutsch", languageName = "Deutsch",
formatNumbers = true,
decimalSeperator = ',',
thousandsSeperator = ' ',
translation = { translation = {
["1a) Offensive Name"] = false, ["1a) Offensive Name"] = false,
["1b) Invalid Name Format"] = false, ["1b) Invalid Name Format"] = false,

View File

@@ -3,6 +3,10 @@ locale = {
charset = "cp1252", charset = "cp1252",
languageName = "English", languageName = "English",
formatNumbers = true,
decimalSeperator = '.',
thousandsSeperator = ',',
-- translations are not needed because everything is already in english -- translations are not needed because everything is already in english
translation = {} translation = {}
} }

View File

@@ -7,6 +7,10 @@ locale = {
charset = "cp1252", charset = "cp1252",
languageName = "Espa<EFBFBD>ol", languageName = "Espa<EFBFBD>ol",
formatNumbers = true,
decimalSeperator = ',',
thousandsSeperator = '.',
translation = { translation = {
["1a) Offensive Name"] = "1a) Nombre ofensivo", ["1a) Offensive Name"] = "1a) Nombre ofensivo",
["1b) Invalid Name Format"] = "1b) Formato invalido para nombre", ["1b) Invalid Name Format"] = "1b) Formato invalido para nombre",

View File

@@ -2,6 +2,10 @@ locale = {
name = "pl", name = "pl",
languageName = "Polski", languageName = "Polski",
formatNumbers = true,
decimalSeperator = '.',
thousandsSeperator = ' ',
translation = { translation = {
["1a) Offensive Name"] = "1a) Obrazliwe Imie", ["1a) Offensive Name"] = "1a) Obrazliwe Imie",
["1b) Invalid Name Format"] = "1b) Niepoprawny Format Imienia", ["1b) Invalid Name Format"] = "1b) Niepoprawny Format Imienia",

View File

@@ -3,6 +3,10 @@ locale = {
charset = "cp1252", charset = "cp1252",
languageName = "Portugu<EFBFBD>s", languageName = "Portugu<EFBFBD>s",
formatNumbers = true,
decimalSeperator = ',',
thousandsSeperator = '.',
-- As tradu<64><75>es devem vir sempre em ordem alfab<61>tica. -- As tradu<64><75>es devem vir sempre em ordem alfab<61>tica.
translation = { translation = {
["%d of experience per hour"] = "%d de experi<72>ncia por hora", ["%d of experience per hour"] = "%d de experi<72>ncia por hora",

View File

@@ -5,6 +5,10 @@ locale = {
charset = "cp1252", charset = "cp1252",
languageName = "Svenska", languageName = "Svenska",
formatNumbers = true,
decimalSeperator = ',',
thousandsSeperator = ' ',
translation = { translation = {
["1a) Offensive Name"] = "1a) Offensivt Namn", ["1a) Offensive Name"] = "1a) Offensivt Namn",
["1b) Invalid Name Format"] = "1b) Ogiltigt Namnformat", ["1b) Invalid Name Format"] = "1b) Ogiltigt Namnformat",

View File

@@ -8,6 +8,7 @@ Button < UIButton
image-clip: 0 0 22 23 image-clip: 0 0 22 23
image-border: 3 image-border: 3
padding: 5 10 5 10 padding: 5 10 5 10
opacity: 1.0
$hover !disabled: $hover !disabled:
image-clip: 0 23 22 23 image-clip: 0 23 22 23
@@ -45,6 +46,7 @@ NextButton < UIButton
size: 12 21 size: 12 21
image-source: /images/ui/arrow_horizontal image-source: /images/ui/arrow_horizontal
image-clip: 12 0 12 21 image-clip: 12 0 12 21
image-color: #ffffff
$hover !disabled: $hover !disabled:
image-clip: 12 21 12 21 image-clip: 12 21 12 21
@@ -53,12 +55,13 @@ NextButton < UIButton
image-clip: 12 21 12 21 image-clip: 12 21 12 21
$disabled: $disabled:
image-color: #dfdfdf55 image-color: #dfdfdf88
PreviousButton < UIButton PreviousButton < UIButton
size: 12 21 size: 12 21
image-source: /images/ui/arrow_horizontal image-source: /images/ui/arrow_horizontal
image-clip: 0 0 12 21 image-clip: 0 0 12 21
image-color: #ffffff
$hover !disabled: $hover !disabled:
image-clip: 0 21 12 21 image-clip: 0 21 12 21
@@ -67,7 +70,7 @@ PreviousButton < UIButton
image-clip: 0 21 12 21 image-clip: 0 21 12 21
$disabled: $disabled:
image-color: #dfdfdf55 image-color: #dfdfdf88
AddButton < UIButton AddButton < UIButton
size: 20 20 size: 20 20

View File

@@ -27,7 +27,7 @@ MoveableTabBarButton < UIButton
color: #dfdfdf color: #dfdfdf
$on !checked: $on !checked:
color: #dfdfdf color: #de6f6f
TabBar < UITabBar TabBar < UITabBar
size: 80 21 size: 80 21
@@ -36,10 +36,11 @@ TabBar < UITabBar
anchors.fill: parent anchors.fill: parent
TabBarPanel < Panel TabBarPanel < Panel
TabBarButton < UIButton TabBarButton < UIButton
size: 22 23 size: 20 21
image-source: /images/ui/tabbutton_square
image-source: /images/ui/tabbutton_square image-source: /images/ui/tabbutton_square
image-color: #dfdfdf image-color: #dfdfdf
image-clip: 0 0 22 23 image-clip: 0 0 20 21
image-border: 3 image-border: 3
image-border-bottom: 0 image-border-bottom: 0
icon-color: #dfdfdf icon-color: #dfdfdf
@@ -55,7 +56,7 @@ TabBarButton < UIButton
margin-left: 5 margin-left: 5
$hover !checked: $hover !checked:
image-clip: 0 23 22 23 image-clip: 0 21 20 21
color: #dfdfdf color: #dfdfdf
$disabled: $disabled:
@@ -63,7 +64,7 @@ TabBarButton < UIButton
icon-color: #dfdfdf icon-color: #dfdfdf
$checked: $checked:
image-clip: 0 46 22 23 image-clip: 0 42 20 21
color: #dfdfdf color: #dfdfdf
$on !checked: $on !checked:
@@ -73,6 +74,14 @@ TabBarRounded < TabBar
TabBarRoundedPanel < TabBarPanel TabBarRoundedPanel < TabBarPanel
TabBarRoundedButton < TabBarButton TabBarRoundedButton < TabBarButton
image-source: /images/ui/tabbutton_rounded image-source: /images/ui/tabbutton_rounded
size: 22 23
image-clip: 0 0 22 23
$hover !checked:
image-clip: 0 23 22 23
$checked:
image-clip: 0 46 22 23
TabBarVertical < UITabBar TabBarVertical < UITabBar
width: 96 width: 96

View File

@@ -1,26 +1,62 @@
Table < UITable Table < UITable
layout: verticalBox layout: verticalBox
header-column-style: HeaderTableColumn header-column-style: TableHeaderColumn
header-row-style: HeaderTableRow header-row-style: TableHeaderRow
column-style: TableColumn column-style: TableColumn
row-style: TableRow row-style: TableRow
TableData < UIScrollArea TableData < UIScrollArea
layout: verticalBox layout: verticalBox
TableRow < Label TableRow < UITableRow
layout: horizontalBox layout: horizontalBox
height: 10 height: 10
text-wrap: true text-wrap: true
focusable: true
even-background-color: alpha
odd-background-color: #00000022
$focus:
background-color: #294f6d
color: #ffffff
TableColumn < Label TableColumn < Label
width: 30 width: 30
text-wrap: true text-wrap: true
focusable: false
TableHeaderRow < Label TableHeaderRow < Label
layout: horizontalBox layout: horizontalBox
focusable: false
height: 10 height: 10
text-wrap: true text-wrap: true
TableHeaderColumn < Button TableHeaderColumn < UITableHeaderColumn
width: 30 font: verdana-11px-antialised
background-color: alpha
color: #dfdfdfff
height: 23
focusable: true
text-offset: 0 0
image-source: /images/ui/button
image-color: #dfdfdf
image-clip: 0 0 22 23
image-border: 3
padding: 5 10 5 10
enabled: false
focusable: false
$hover !disabled:
image-clip: 0 23 22 23
$pressed:
image-clip: 0 46 22 23
text-offset: 1 1
$disabled:
color: #dfdfdf88
opacity: 0.8
SortableTableHeaderColumn < TableHeaderColumn
enabled: true
focusable: true

View File

@@ -27,7 +27,7 @@ local function tryLogin(charInfo, tries)
CharacterList.hide() CharacterList.hide()
g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName) g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, G.authenticatorToken, G.sessionKey)
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...')) loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...'))
connect(loadBox, { onCancel = function() connect(loadBox, { onCancel = function()
@@ -109,6 +109,16 @@ function onGameLoginError(message)
end end
end end
function onGameLoginToken(unknown)
CharacterList.destroyLoadBox()
-- TODO: make it possible to enter a new token here / prompt token
errorBox = displayErrorBox(tr("Two-Factor Authentification"), 'A new authentification token is required.\nPlease login again.')
errorBox.onOk = function()
errorBox = nil
EnterGame.show()
end
end
function onGameConnectionError(message, code) function onGameConnectionError(message, code)
CharacterList.destroyLoadBox() CharacterList.destroyLoadBox()
local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message) local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message)
@@ -131,6 +141,7 @@ end
-- public functions -- public functions
function CharacterList.init() function CharacterList.init()
connect(g_game, { onLoginError = onGameLoginError }) connect(g_game, { onLoginError = onGameLoginError })
connect(g_game, { onLoginToken = onGameLoginToken })
connect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) connect(g_game, { onUpdateNeeded = onGameUpdateNeeded })
connect(g_game, { onConnectionError = onGameConnectionError }) connect(g_game, { onConnectionError = onGameConnectionError })
connect(g_game, { onGameStart = CharacterList.destroyLoadBox }) connect(g_game, { onGameStart = CharacterList.destroyLoadBox })
@@ -144,6 +155,7 @@ end
function CharacterList.terminate() function CharacterList.terminate()
disconnect(g_game, { onLoginError = onGameLoginError }) disconnect(g_game, { onLoginError = onGameLoginError })
disconnect(g_game, { onLoginToken = onGameLoginToken })
disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded }) disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded })
disconnect(g_game, { onConnectionError = onGameConnectionError }) disconnect(g_game, { onConnectionError = onGameConnectionError })
disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox }) disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox })

View File

@@ -16,7 +16,7 @@ CharacterWidget < UIWidget
Label Label
id: name id: name
color: #aaaaaa color: #bbbbbb
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
font: verdana-11px-monochrome font: verdana-11px-monochrome
@@ -29,8 +29,7 @@ CharacterWidget < UIWidget
Label Label
id: worldName id: worldName
color: #ffffff color: #bbbbbb
color: #aaaaaa
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
margin-right: 5 margin-right: 5
@@ -45,16 +44,21 @@ CharacterWidget < UIWidget
MainWindow MainWindow
id: charactersWindow id: charactersWindow
!text: tr('Character List') !text: tr('Character List')
size: 250 248
visible: false visible: false
@onEnter: CharacterList.doLogin() @onEnter: CharacterList.doLogin()
@onEscape: CharacterList.hide(true) @onEscape: CharacterList.hide(true)
@onSetup: | @onSetup: |
g_keyboard.bindKeyPress('Up', function() self:getChildById('characters'):focusPreviousChild(KeyboardFocusReason) end, self) g_keyboard.bindKeyPress('Up', function() self:getChildById('characters'):focusPreviousChild(KeyboardFocusReason) end, self)
g_keyboard.bindKeyPress('Down', function() self:getChildById('characters'):focusNextChild(KeyboardFocusReason) end, self) g_keyboard.bindKeyPress('Down', function() self:getChildById('characters'):focusNextChild(KeyboardFocusReason) end, self)
if g_game.getFeature(GamePreviewState) then
self:setSize({width = 350, height = 400})
else
self:setSize({width = 250, height = 248})
end
TextList TextList
id: characters id: characters
background-color: #565656
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: characterListScrollBar.left anchors.right: characterListScrollBar.left

View File

@@ -33,10 +33,17 @@ local function onMotd(protocol, motd)
end end
end end
local function onSessionKey(protocol, sessionKey)
G.sessionKey = sessionKey
end
local function onCharacterList(protocol, characters, account, otui) local function onCharacterList(protocol, characters, account, otui)
-- Try add server to the server list -- Try add server to the server list
ServerList.add(G.host, G.port, g_game.getClientVersion()) ServerList.add(G.host, G.port, g_game.getClientVersion())
-- Save 'Stay logged in' setting
g_settings.set('staylogged', enterGame:getChildById('stayLoggedBox'):isChecked())
if enterGame:getChildById('rememberPasswordBox'):isChecked() then if enterGame:getChildById('rememberPasswordBox'):isChecked() then
local account = g_crypt.encrypt(G.account) local account = g_crypt.encrypt(G.account)
local password = g_crypt.encrypt(G.password) local password = g_crypt.encrypt(G.password)
@@ -59,13 +66,19 @@ local function onCharacterList(protocol, characters, account, otui)
loadBox:destroy() loadBox:destroy()
loadBox = nil loadBox = nil
for _, characterInfo in pairs(characters) do
if characterInfo.previewState and characterInfo.previewState ~= PreviewState.Default then
characterInfo.worldName = characterInfo.worldName .. ', Preview'
end
end
CharacterList.create(characters, account, otui) CharacterList.create(characters, account, otui)
CharacterList.show() CharacterList.show()
if motdEnabled then if motdEnabled then
local lastMotdNumber = g_settings.getNumber("motd") local lastMotdNumber = g_settings.getNumber("motd")
if G.motdNumber and G.motdNumber ~= lastMotdNumber then if G.motdNumber and G.motdNumber ~= lastMotdNumber then
g_settings.set("motd", motdNumber) g_settings.set("motd", G.motdNumber)
motdWindow = displayInfoBox(tr('Message of the day'), G.motdMessage) motdWindow = displayInfoBox(tr('Message of the day'), G.motdMessage)
connect(motdWindow, { onOk = function() CharacterList.show() motdWindow = nil end }) connect(motdWindow, { onOk = function() CharacterList.show() motdWindow = nil end })
CharacterList.hide() CharacterList.hide()
@@ -103,9 +116,10 @@ function EnterGame.init()
local password = g_settings.get('password') local password = g_settings.get('password')
local host = g_settings.get('host') local host = g_settings.get('host')
local port = g_settings.get('port') local port = g_settings.get('port')
local stayLogged = g_settings.getBoolean('staylogged')
local autologin = g_settings.getBoolean('autologin') local autologin = g_settings.getBoolean('autologin')
local clientVersion = g_settings.getInteger('client-version') local clientVersion = g_settings.getInteger('client-version')
if clientVersion == 0 then clientVersion = 860 end if clientVersion == 0 then clientVersion = 1074 end
if port == nil or port == 0 then port = 7171 end if port == nil or port == 0 then port = 7171 end
@@ -115,6 +129,7 @@ function EnterGame.init()
enterGame:getChildById('serverHostTextEdit'):setText(host) enterGame:getChildById('serverHostTextEdit'):setText(host)
enterGame:getChildById('serverPortTextEdit'):setText(port) enterGame:getChildById('serverPortTextEdit'):setText(port)
enterGame:getChildById('autoLoginBox'):setChecked(autologin) enterGame:getChildById('autoLoginBox'):setChecked(autologin)
enterGame:getChildById('stayLoggedBox'):setChecked(stayLogged)
clientBox = enterGame:getChildById('clientComboBox') clientBox = enterGame:getChildById('clientComboBox')
for _, proto in pairs(g_game.getSupportedClients()) do for _, proto in pairs(g_game.getSupportedClients()) do
@@ -122,6 +137,10 @@ function EnterGame.init()
end end
clientBox:setCurrentOption(clientVersion) clientBox:setCurrentOption(clientVersion)
EnterGame.toggleAuthenticatorToken(clientVersion, true)
EnterGame.toggleStayLoggedBox(clientVersion, true)
connect(clientBox, { onOptionChange = EnterGame.onClientVersionChange })
enterGame:hide() enterGame:hide()
if g_app.isRunning() and not g_game.isOnline() then if g_app.isRunning() and not g_game.isOnline() then
@@ -146,6 +165,7 @@ end
function EnterGame.terminate() function EnterGame.terminate()
g_keyboard.unbindKeyDown('Ctrl+G') g_keyboard.unbindKeyDown('Ctrl+G')
disconnect(clientBox, { onOptionChange = EnterGame.onClientVersionChange })
enterGame:destroy() enterGame:destroy()
enterGame = nil enterGame = nil
enterGameButton:destroy() enterGameButton:destroy()
@@ -204,14 +224,80 @@ end
function EnterGame.clearAccountFields() function EnterGame.clearAccountFields()
enterGame:getChildById('accountNameTextEdit'):clearText() enterGame:getChildById('accountNameTextEdit'):clearText()
enterGame:getChildById('accountPasswordTextEdit'):clearText() enterGame:getChildById('accountPasswordTextEdit'):clearText()
enterGame:getChildById('authenticatorTokenTextEdit'):clearText()
enterGame:getChildById('accountNameTextEdit'):focus() enterGame:getChildById('accountNameTextEdit'):focus()
g_settings.remove('account') g_settings.remove('account')
g_settings.remove('password') g_settings.remove('password')
end end
function EnterGame.toggleAuthenticatorToken(clientVersion, init)
local enabled = (clientVersion >= 1072)
if enabled == enterGame.authenticatorEnabled then
return
end
enterGame:getChildById('authenticatorTokenLabel'):setOn(enabled)
enterGame:getChildById('authenticatorTokenTextEdit'):setOn(enabled)
local newHeight = enterGame:getHeight()
local newY = enterGame:getY()
if enabled then
newY = newY - enterGame.authenticatorHeight
newHeight = newHeight + enterGame.authenticatorHeight
else
newY = newY + enterGame.authenticatorHeight
newHeight = newHeight - enterGame.authenticatorHeight
end
if not init then
enterGame:breakAnchors()
enterGame:setY(newY)
enterGame:bindRectToParent()
end
enterGame:setHeight(newHeight)
enterGame.authenticatorEnabled = enabled
end
function EnterGame.toggleStayLoggedBox(clientVersion, init)
local enabled = (clientVersion >= 1074)
if enabled == enterGame.stayLoggedBoxEnabled then
return
end
enterGame:getChildById('stayLoggedBox'):setOn(enabled)
local newHeight = enterGame:getHeight()
local newY = enterGame:getY()
if enabled then
newY = newY - enterGame.stayLoggedBoxHeight
newHeight = newHeight + enterGame.stayLoggedBoxHeight
else
newY = newY + enterGame.stayLoggedBoxHeight
newHeight = newHeight - enterGame.stayLoggedBoxHeight
end
if not init then
enterGame:breakAnchors()
enterGame:setY(newY)
enterGame:bindRectToParent()
end
enterGame:setHeight(newHeight)
enterGame.stayLoggedBoxEnabled = enabled
end
function EnterGame.onClientVersionChange(comboBox, text, data)
local clientVersion = tonumber(text)
EnterGame.toggleAuthenticatorToken(clientVersion)
EnterGame.toggleStayLoggedBox(clientVersion)
end
function EnterGame.doLogin() function EnterGame.doLogin()
G.account = enterGame:getChildById('accountNameTextEdit'):getText() G.account = enterGame:getChildById('accountNameTextEdit'):getText()
G.password = enterGame:getChildById('accountPasswordTextEdit'):getText() G.password = enterGame:getChildById('accountPasswordTextEdit'):getText()
G.authenticatorToken = enterGame:getChildById('authenticatorTokenTextEdit'):getText()
G.stayLogged = enterGame:getChildById('stayLoggedBox'):isChecked()
G.host = enterGame:getChildById('serverHostTextEdit'):getText() G.host = enterGame:getChildById('serverHostTextEdit'):getText()
G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText()) G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText())
local clientVersion = tonumber(clientBox:getText()) local clientVersion = tonumber(clientBox:getText())
@@ -230,6 +316,7 @@ function EnterGame.doLogin()
protocolLogin = ProtocolLogin.create() protocolLogin = ProtocolLogin.create()
protocolLogin.onLoginError = onError protocolLogin.onLoginError = onError
protocolLogin.onMotd = onMotd protocolLogin.onMotd = onMotd
protocolLogin.onSessionKey = onSessionKey
protocolLogin.onCharacterList = onCharacterList protocolLogin.onCharacterList = onCharacterList
protocolLogin.onUpdateNeeded = onUpdateNeeded protocolLogin.onUpdateNeeded = onUpdateNeeded
@@ -240,12 +327,12 @@ function EnterGame.doLogin()
EnterGame.show() EnterGame.show()
end }) end })
g_game.chooseRsa(G.host)
g_game.setClientVersion(clientVersion) g_game.setClientVersion(clientVersion)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(clientVersion)) g_game.setProtocolVersion(g_game.getClientProtocolVersion(clientVersion))
g_game.chooseRsa(G.host)
if modules.game_things.isLoaded() then if modules.game_things.isLoaded() then
protocolLogin:login(G.host, G.port, G.account, G.password) protocolLogin:login(G.host, G.port, G.account, G.password, G.authenticatorToken, G.stayLogged)
else else
loadBox:destroy() loadBox:destroy()
loadBox = nil loadBox = nil
@@ -266,6 +353,7 @@ function EnterGame.setDefaultServer(host, port, protocol)
local clientLabel = enterGame:getChildById('clientLabel') local clientLabel = enterGame:getChildById('clientLabel')
local accountTextEdit = enterGame:getChildById('accountNameTextEdit') local accountTextEdit = enterGame:getChildById('accountNameTextEdit')
local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit') local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit')
local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit')
if hostTextEdit:getText() ~= host then if hostTextEdit:getText() ~= host then
hostTextEdit:setText(host) hostTextEdit:setText(host)
@@ -273,6 +361,7 @@ function EnterGame.setDefaultServer(host, port, protocol)
clientBox:setCurrentOption(protocol) clientBox:setCurrentOption(protocol)
accountTextEdit:setText('') accountTextEdit:setText('')
passwordTextEdit:setText('') passwordTextEdit:setText('')
authenticatorTokenTextEdit:setText('')
end end
end end
@@ -286,6 +375,16 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
portTextEdit:setVisible(false) portTextEdit:setVisible(false)
portTextEdit:setHeight(0) portTextEdit:setHeight(0)
local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit')
authenticatorTokenTextEdit:setText('')
authenticatorTokenTextEdit:setOn(false)
local authenticatorTokenLabel = enterGame:getChildById('authenticatorTokenLabel')
authenticatorTokenLabel:setOn(false)
local stayLoggedBox = enterGame:getChildById('stayLoggedBox')
stayLoggedBox:setChecked(false)
stayLoggedBox:setOn(false)
clientBox:setCurrentOption(protocol) clientBox:setCurrentOption(protocol)
clientBox:setVisible(false) clientBox:setVisible(false)
clientBox:setHeight(0) clientBox:setHeight(0)
@@ -306,11 +405,11 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
serverListButton:setWidth(0) serverListButton:setWidth(0)
local rememberPasswordBox = enterGame:getChildById('rememberPasswordBox') local rememberPasswordBox = enterGame:getChildById('rememberPasswordBox')
rememberPasswordBox:setMarginTop(-5) rememberPasswordBox:setMarginTop(-8)
if not windowWidth then windowWidth = 236 end if not windowWidth then windowWidth = 236 end
enterGame:setWidth(windowWidth) enterGame:setWidth(windowWidth)
if not windowHeight then windowHeight = 200 end if not windowHeight then windowHeight = 210 end
enterGame:setHeight(windowHeight) enterGame:setHeight(windowHeight)
end end

View File

@@ -1,6 +1,6 @@
EnterGameWindow < MainWindow EnterGameWindow < MainWindow
!text: tr('Enter Game') !text: tr('Enter Game')
size: 236 274 size: 236 298
EnterGameButton < Button EnterGameButton < Button
width: 64 width: 64
@@ -21,6 +21,10 @@ ServerListButton < UIButton
EnterGameWindow EnterGameWindow
id: enterGame id: enterGame
&authenticatorEnabled: false
&authenticatorHeight: 44
&stayLoggedBoxEnabled: false
&stayLoggedBoxHeight: 24
@onEnter: EnterGame.doLogin() @onEnter: EnterGame.doLogin()
MenuLabel MenuLabel
@@ -50,6 +54,52 @@ EnterGameWindow
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 2 margin-top: 2
MenuLabel
id: authenticatorTokenLabel
!text: tr('Authenticator Token')
anchors.left: prev.left
anchors.top: prev.bottom
text-auto-resize: true
margin-top: -12
visible: false
$on:
visible: true
margin-top: 8
TextEdit
id: authenticatorTokenTextEdit
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: -22
visible: false
max-length: 8
$on:
visible: true
margin-top: 2
CheckBox
id: stayLoggedBox
!text: tr('Stay logged during session')
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 6
margin-top: -16
visible: false
$on:
visible: true
margin-top: 8
HorizontalSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 8
MenuLabel MenuLabel
id: serverLabel id: serverLabel
!text: tr('Server') !text: tr('Server')
@@ -132,16 +182,24 @@ EnterGameWindow
anchors.top: prev.bottom anchors.top: prev.bottom
margin-top: 2 margin-top: 2
HorizontalSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 6
EnterGameButton EnterGameButton
!text: tr('Ok') !text: tr('Ok')
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.top: prev.bottom
margin-top: 4
@onClick: EnterGame.doLogin() @onClick: EnterGame.doLogin()
Label Label
id: serverInfoLabel id: serverInfoLabel
font: verdana-11px-rounded font: verdana-11px-rounded
anchors.bottom: parent.bottom anchors.top: prev.top
anchors.left: parent.left anchors.left: parent.left
margin-top: 5
color: green color: green
text-auto-resize: true text-auto-resize: true

View File

@@ -166,16 +166,20 @@ end
-- global function used to translate texts -- global function used to translate texts
function _G.tr(text, ...) function _G.tr(text, ...)
if currentLocale then if currentLocale then
if tonumber(text) then if tonumber(text) and currentLocale.formatNumbers then
-- todo: use locale information to calculate this. also detect floating numbers local number = tostring(text):split('.')
local out = '' local out = ''
local number = tostring(text):reverse() local reverseNumber = number[1]:reverse()
for i=1,#number do for i=1,#reverseNumber do
out = out .. number:sub(i, i) out = out .. reverseNumber:sub(i, i)
if i % 3 == 0 and i ~= #number then if i % 3 == 0 and i ~= #number then
out = out .. ',' out = out .. currentLocale.thousandsSeperator
end end
end end
if number[2] then
out = number[2] .. currentLocale.decimalSeperator .. out
end
return out:reverse() return out:reverse()
elseif tostring(text) then elseif tostring(text) then
local translation = currentLocale.translation[text] local translation = currentLocale.translation[text]

View File

@@ -19,7 +19,7 @@ function AddServer.add()
local added, error = ServerList.add(host, port, protocol) local added, error = ServerList.add(host, port, protocol)
if not added then if not added then
displayErrorBox(tr('Add Error'), tr(error)) displayErrorBox(tr('Error'), tr(error))
else else
AddServer.hide() AddServer.hide()
end end

View File

@@ -12,7 +12,9 @@ function ServerList.init()
serverTextList = serverListWindow:getChildById('serverList') serverTextList = serverListWindow:getChildById('serverList')
servers = g_settings.getNode('ServerList') or {} servers = g_settings.getNode('ServerList') or {}
ServerList.load() if servers then
ServerList.load()
end
end end
function ServerList.terminate() function ServerList.terminate()
@@ -24,8 +26,8 @@ function ServerList.terminate()
end end
function ServerList.load() function ServerList.load()
for k,server in pairs(servers) do for host, server in pairs(servers) do
ServerList.add(k, server.port, server.protocol, true) ServerList.add(host, server.port, server.protocol, true)
end end
end end
@@ -43,7 +45,9 @@ function ServerList.select()
end end
function ServerList.add(host, port, protocol, load) function ServerList.add(host, port, protocol, load)
if not load and servers[host] then if not host or not port or not protocol then
return false, 'Failed to load settings'
elseif not load and servers[host] then
return false, 'Server already exists' return false, 'Server already exists'
elseif host == '' or port == '' then elseif host == '' or port == '' then
return false, 'Required fields are missing' return false, 'Required fields are missing'

View File

@@ -30,6 +30,10 @@ local allLines = {}
-- private functions -- private functions
local function navigateCommand(step) local function navigateCommand(step)
if commandTextEdit:isMultiline() then
return
end
local numCommands = #commandHistory local numCommands = #commandHistory
if numCommands > 0 then if numCommands > 0 then
currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands) currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands)
@@ -96,16 +100,29 @@ local function completeCommand()
end end
end end
local function doCommand() local function doCommand(textWidget)
local currentCommand = commandTextEdit:getText() local currentCommand = textWidget:getText()
executeCommand(currentCommand) executeCommand(currentCommand)
textWidget:clearText()
if commandTextEdit then
commandTextEdit:clearText()
end
return true return true
end end
local function addNewline(textWidget)
if not textWidget:isOn() then
textWidget:setOn(true)
end
textWidget:appendText('\n')
end
local function onCommandChange(textWidget, newText, oldText)
local _, newLineCount = string.gsub(newText, '\n', '\n')
textWidget:setHeight((newLineCount + 1) * textWidget.baseHeight)
if newLineCount == 0 and textWidget:isOn() then
textWidget:setOn(false)
end
end
local function onLog(level, message, time) local function onLog(level, message, time)
if disabled then return end if disabled then return end
-- avoid logging while reporting logs (would cause a infinite loop) -- avoid logging while reporting logs (would cause a infinite loop)
@@ -129,6 +146,8 @@ function init()
commandHistory = g_settings.getList('terminal-history') commandHistory = g_settings.getList('terminal-history')
commandTextEdit = terminalWindow:getChildById('commandTextEdit') commandTextEdit = terminalWindow:getChildById('commandTextEdit')
commandTextEdit:setHeight(commandTextEdit.baseHeight)
connect(commandTextEdit, {onTextChange = onCommandChange})
g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit) g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit)
g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit) g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit)
g_keyboard.bindKeyPress('Ctrl+C', g_keyboard.bindKeyPress('Ctrl+C',
@@ -138,6 +157,7 @@ function init()
return true return true
end, commandTextEdit) end, commandTextEdit)
g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit) g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit)
g_keyboard.bindKeyPress('Shift+Enter', addNewline, commandTextEdit)
g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit) g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit)
g_keyboard.bindKeyDown('Escape', hide, terminalWindow) g_keyboard.bindKeyDown('Escape', hide, terminalWindow)
@@ -293,7 +313,7 @@ function addLine(text, color)
end end
function executeCommand(command) function executeCommand(command)
if command == nil or #command == 0 then return end if command == nil or #string.gsub(command, '\n', '') == 0 then return end
-- add command line -- add command line
addLine("> " .. command, "#ffffff") addLine("> " .. command, "#ffffff")

View File

@@ -47,7 +47,7 @@ UIWindow
anchors.left: parent.left anchors.left: parent.left
anchors.right: terminalScroll.left anchors.right: terminalScroll.left
anchors.top: terminalScroll.top anchors.top: terminalScroll.top
anchors.bottom: commandSymbolLabel.top anchors.bottom: commandTextEdit.top
layout: layout:
type: verticalBox type: verticalBox
align-bottom: true align-bottom: true
@@ -80,14 +80,25 @@ UIWindow
UITextEdit UITextEdit
id: commandTextEdit id: commandTextEdit
height: 12 background: #aaaaaa11
border-color: #aaaaaa88
&baseHeight: 12
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: commandSymbolLabel.right anchors.left: commandSymbolLabel.right
anchors.right: parent.right anchors.right: terminalScroll.left
margin-left: 1 margin-left: 1
padding-left: 2
font: terminus-10px font: terminus-10px
selection-color: black selection-color: black
selection-background-color: white selection-background-color: white
border-width-left: 0
border-width-top: 0
multiline: false
$on:
border-width-left: 1
border-width-top: 1
multiline: true
ResizeBorder ResizeBorder
id: bottomResizeBorder id: bottomResizeBorder

View File

@@ -10,6 +10,7 @@ Module
dofile 'string' dofile 'string'
dofile 'table' dofile 'table'
dofile 'bitwise' dofile 'bitwise'
dofile 'struct'
dofile 'const' dofile 'const'
dofile 'util' dofile 'util'

173
modules/corelib/struct.lua Normal file
View File

@@ -0,0 +1,173 @@
Struct = {}
function Struct.pack(format, ...)
local stream = {}
local vars = {...}
local endianness = true
for i = 1, format:len() do
local opt = format:sub(i, i)
if opt == '>' then
endianness = false
elseif opt:find('[bBhHiIlL]') then
local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1
local val = tonumber(table.remove(vars, 1))
if val < 0 then
val = val + 2 ^ (n * 8 - 1)
end
local bytes = {}
for j = 1, n do
table.insert(bytes, string.char(val % (2 ^ 8)))
val = math.floor(val / (2 ^ 8))
end
if not endianness then
table.insert(stream, string.reverse(table.concat(bytes)))
else
table.insert(stream, table.concat(bytes))
end
elseif opt:find('[fd]') then
local val = tonumber(table.remove(vars, 1))
local sign = 0
if val < 0 then
sign = 1
val = -val
end
local mantissa, exponent = math.frexp(val)
if val == 0 then
mantissa = 0
exponent = 0
else
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24)
exponent = exponent + ((opt == 'd') and 1022 or 126)
end
local bytes = {}
if opt == 'd' then
val = mantissa
for i = 1, 6 do
table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
val = math.floor(val / (2 ^ 8))
end
else
table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8)))
val = math.floor(mantissa / (2 ^ 8))
table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
val = math.floor(val / (2 ^ 8))
end
table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8)))
val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8))
table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8)))
val = math.floor((sign * 128 + val) / (2 ^ 8))
if not endianness then
table.insert(stream, string.reverse(table.concat(bytes)))
else
table.insert(stream, table.concat(bytes))
end
elseif opt == 's' then
table.insert(stream, tostring(table.remove(vars, 1)))
table.insert(stream, string.char(0))
elseif opt == 'c' then
local n = format:sub(i + 1):match('%d+')
local length = tonumber(n)
if length > 0 then
local str = tostring(table.remove(vars, 1))
if length - str:len() > 0 then
str = str .. string.rep(' ', length - str:len())
end
table.insert(stream, str:sub(1, length))
end
i = i + n:len()
end
end
return table.concat(stream)
end
function Struct.unpack(format, stream)
local vars = {}
local iterator = 1
local endianness = true
for i = 1, format:len() do
local opt = format:sub(i, i)
if opt == '>' then
endianness = false
elseif opt:find('[bBhHiIlL]') then
local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1
local signed = opt:lower() == opt
local val = 0
for j = 1, n do
local byte = string.byte(stream:sub(iterator, iterator))
if endianness then
val = val + byte * (2 ^ ((j - 1) * 8))
else
val = val + byte * (2 ^ ((n - j) * 8))
end
iterator = iterator + 1
end
if signed then
val = val - 2 ^ (n * 8 - 1)
end
table.insert(vars, val)
elseif opt:find('[fd]') then
local n = (opt == 'd') and 8 or 4
local x = stream:sub(iterator, iterator + n - 1)
iterator = iterator + n
if not endianness then
x = string.reverse(x)
end
local sign = 1
local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128)
for i = n - 2, 1, -1 do
mantissa = mantissa * (2 ^ 8) + string.byte(x, i)
end
if string.byte(x, n) > 127 then
sign = -1
end
local exponent = (string.byte(x, n) % 128) * ((opt == 'd') and 16 or 2) + math.floor(string.byte(x, n - 1) / ((opt == 'd') and 16 or 128))
if exponent == 0 then
table.insert(vars, 0.0)
else
mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign
table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127)))
end
elseif opt == 's' then
local bytes = {}
for j = iterator, stream:len() do
if stream:sub(j, j) == string.char(0) then
break
end
table.insert(bytes, stream:sub(j, j))
end
local str = table.concat(bytes)
iterator = iterator + str:len() + 1
table.insert(vars, str)
elseif opt == 'c' then
local n = format:sub(i + 1):match('%d+')
table.insert(vars, stream:sub(iterator, iterator + tonumber(n)))
iterator = iterator + tonumber(n)
i = i + n:len()
end
end
return unpack(vars)
end

View File

@@ -6,7 +6,7 @@ local function onTabClick(tab)
tab.tabBar:selectTab(tab) tab.tabBar:selectTab(tab)
end end
local function updateMargins(tabBar, ignored) local function updateMargins(tabBar)
if #tabBar.tabs == 0 then return end if #tabBar.tabs == 0 then return end
local currentMargin = 0 local currentMargin = 0
@@ -17,19 +17,19 @@ local function updateMargins(tabBar, ignored)
end end
local function updateNavigation(tabBar) local function updateNavigation(tabBar)
if prevNavigation then if tabBar.prevNavigation then
if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then
prevNavigation:enable() tabBar.prevNavigation:enable()
else else
prevNavigation:disable() tabBar.prevNavigation:disable()
end end
end end
if nextNavigation then if tabBar.nextNavigation then
if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then
nextNavigation:enable() tabBar.nextNavigation:enable()
else else
nextNavigation:disable() tabBar.nextNavigation:disable()
end end
end end
end end
@@ -187,7 +187,7 @@ local function onTabDragMove(tab, mousePos, mouseMoved)
end end
local function tabBlink(tab, step) local function tabBlink(tab, step)
step = step or 0 local step = step or 0
tab:setOn(not tab:isOn()) tab:setOn(not tab:isOn())
removeEvent(tab.blinkEvent) removeEvent(tab.blinkEvent)
@@ -218,6 +218,19 @@ function UIMoveableTabBar.create()
return tabbar return tabbar
end end
function UIMoveableTabBar:onDestroy()
if self.prevNavigation then
self.prevNavigation:disable()
end
if self.nextNavigation then
self.nextNavigation:disable()
end
self.nextNavigation = nil
self.prevNavigation = nil
end
function UIMoveableTabBar:setContentWidget(widget) function UIMoveableTabBar:setContentWidget(widget)
self.contentWidget = widget self.contentWidget = widget
if #self.tabs > 0 then if #self.tabs > 0 then
@@ -296,8 +309,11 @@ function UIMoveableTabBar:moveTab(tab, units)
end end
function UIMoveableTabBar:onStyleApply(styleName, styleNode) function UIMoveableTabBar:onStyleApply(styleName, styleNode)
if styleNode['moveable'] then if styleNode['movable'] then
self.tabsMoveable = styleNode['moveable'] self.tabsMoveable = styleNode['movable']
end
if styleNode['tab-spacing'] then
self:setTabSpacing(styleNode['tab-spacing'])
end end
end end
@@ -467,14 +483,14 @@ function UIMoveableTabBar:getCurrentTab()
end end
function UIMoveableTabBar:setNavigation(prevButton, nextButton) function UIMoveableTabBar:setNavigation(prevButton, nextButton)
prevNavigation = prevButton self.prevNavigation = prevButton
nextNavigation = nextButton self.nextNavigation = nextButton
if prevNavigation then if self.prevNavigation then
prevNavigation.onClick = function() self:selectPrevTab() end self.prevNavigation.onClick = function() self:selectPrevTab() end
end end
if nextNavigation then if self.nextNavigation then
nextNavigation.onClick = function() self:selectNextTab() end self.nextNavigation.onClick = function() self:selectNextTab() end
end end
updateNavigation(self) updateNavigation(self)
end end

View File

@@ -8,6 +8,7 @@ function UIPopupMenu.create()
local layout = UIVerticalLayout.create(menu) local layout = UIVerticalLayout.create(menu)
layout:setFitChildren(true) layout:setFitChildren(true)
menu:setLayout(layout) menu:setLayout(layout)
menu.isGameMenu = false
return menu return menu
end end
@@ -34,6 +35,7 @@ function UIPopupMenu:display(pos)
rootWidget:addChild(self) rootWidget:addChild(self)
self:setPosition(pos) self:setPosition(pos)
self:grabMouse() self:grabMouse()
self:focus()
--self:grabKeyboard() --self:grabKeyboard()
currentMenu = self currentMenu = self
end end
@@ -76,6 +78,10 @@ function UIPopupMenu:addSeparator()
g_ui.createWidget(self:getStyleName() .. 'Separator', self) g_ui.createWidget(self:getStyleName() .. 'Separator', self)
end end
function UIPopupMenu:setGameMenu(state)
self.isGameMenu = state
end
function UIPopupMenu:onDestroy() function UIPopupMenu:onDestroy()
if currentMenu == self then if currentMenu == self then
currentMenu = nil currentMenu = nil
@@ -105,4 +111,12 @@ local function onRootGeometryUpdate()
currentMenu:destroy() currentMenu:destroy()
end end
end end
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate} )
local function onGameEnd()
if currentMenu and currentMenu.isGameMenu then
currentMenu:destroy()
end
end
connect(rootWidget, { onGeometryChange = onRootGeometryUpdate })
connect(g_game, { onGameEnd = onGameEnd } )

View File

@@ -28,7 +28,7 @@ local function calcValues(self)
proportion = math.min(math.max(self.step, 1), range)/range proportion = math.min(math.max(self.step, 1), range)/range
end end
local px = math.max(proportion * pxrange, 10) local px = math.max(proportion * pxrange, 6)
px = px - px % 2 + 1 px = px - px % 2 + 1
local offset = 0 local offset = 0

View File

@@ -12,7 +12,7 @@ function UISpinBox.create()
spinbox.step = 1 spinbox.step = 1
spinbox.firstchange = true spinbox.firstchange = true
spinbox.mouseScroll = true spinbox.mouseScroll = true
spinbox:setText("0") spinbox:setText("1")
spinbox:setValue(1) spinbox:setValue(1)
return spinbox return spinbox
end end
@@ -23,7 +23,7 @@ function UISpinBox:onSetup()
end end
function UISpinBox:onMouseWheel(mousePos, direction) function UISpinBox:onMouseWheel(mousePos, direction)
if not self.mouseScroll then if not self.mouseScroll then
return false return false
end end
if direction == MouseWheelUp then if direction == MouseWheelUp then
@@ -66,7 +66,15 @@ function UISpinBox:onTextChange(text, oldText)
end end
function UISpinBox:onValueChange(value) function UISpinBox:onValueChange(value)
-- nothing todo -- nothing to do
end
function UISpinBox:onFocusChange(focused)
if not focused then
if self:getText():len() == 0 then
self:setText(self.minimum)
end
end
end end
function UISpinBox:onStyleApply(styleName, styleNode) function UISpinBox:onStyleApply(styleName, styleNode)
@@ -109,14 +117,16 @@ function UISpinBox:down()
self:setValue(self.value - self.step) self:setValue(self.value - self.step)
end end
function UISpinBox:setValue(value) function UISpinBox:setValue(value, dontSignal)
value = value or 0 value = value or 0
value = math.max(math.min(self.maximum, value), self.minimum) value = math.max(math.min(self.maximum, value), self.minimum)
if value == self.value then return end if value == self.value then return end
self.value = value
if self:getText():len() > 0 then if self:getText():len() > 0 then
self:setText(value) self:setText(value)
end end
self.value = value
local upButton = self:getChildById('up') local upButton = self:getChildById('up')
local downButton = self:getChildById('down') local downButton = self:getChildById('down')
@@ -127,7 +137,9 @@ function UISpinBox:setValue(value)
downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum) downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum)
end end
signalcall(self.onValueChange, self, value) if not dontSignal then
signalcall(self.onValueChange, self, value)
end
end end
function UISpinBox:getValue() function UISpinBox:getValue()

View File

@@ -38,6 +38,7 @@ function UITabBar:addTab(text, panel, icon)
end end
local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel) local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self.buttonsPanel)
panel.isTab = true panel.isTab = true
tab.tabPanel = panel tab.tabPanel = panel
tab.tabBar = self tab.tabBar = self

View File

@@ -3,33 +3,45 @@
TODO: TODO:
* Make table headers more robust. * Make table headers more robust.
* Get dynamic row heights working with text wrapping. * Get dynamic row heights working with text wrapping.
* Every second row different background color applied.
]] ]]
TABLE_SORTING_ASC = 0
TABLE_SORTING_DESC = 1
UITable = extends(UIWidget, "UITable") UITable = extends(UIWidget, "UITable")
local HEADER_ID = 'row0' -- Initialize default values
function UITable.create() function UITable.create()
local table = UITable.internalCreate() local table = UITable.internalCreate()
table.headerRow = nil table.headerRow = nil
table.headerColumns = {}
table.dataSpace = nil table.dataSpace = nil
table.rows = {} table.rows = {}
table.rowBaseStyle = nil table.rowBaseStyle = nil
table.columns = {} table.columns = {}
table.columnWidth = {}
table.columBaseStyle = nil table.columBaseStyle = nil
table.headerRowBaseStyle = nil table.headerRowBaseStyle = nil
table.headerColumnBaseStyle = nil table.headerColumnBaseStyle = nil
table.selectedRow = nil table.selectedRow = nil
table.defaultColumnWidth = 80
table.sortColumn = -1
table.sortType = TABLE_SORTING_ASC
table.autoSort = false
return table return table
end end
-- Clear table values
function UITable:onDestroy() function UITable:onDestroy()
for k,row in pairs(self.rows) do for _,row in pairs(self.rows) do
row.onClick = nil row.onClick = nil
end end
self.rows = {} self.rows = {}
self.columns = {} self.columns = {}
self.headerRow = {} self.headerRow = nil
self.headerColumns = {}
self.columnWidth = {}
self.selectedRow = nil self.selectedRow = nil
if self.dataSpace then if self.dataSpace then
@@ -38,36 +50,58 @@ function UITable:onDestroy()
end end
end end
-- Detect if a header is already defined
function UITable:onSetup()
local header = self:getChildById('header')
if header then
self:setHeader(header)
end
end
-- Parse table related styles
function UITable:onStyleApply(styleName, styleNode) function UITable:onStyleApply(styleName, styleNode)
for name, value in pairs(styleNode) do for name, value in pairs(styleNode) do
if name == 'table-data' then if value ~= false then
addEvent(function() if name == 'table-data' then
self:setTableData(self:getParent():getChildById(value)) addEvent(function()
end) self:setTableData(self:getParent():getChildById(value))
elseif name == 'column-style' then end)
addEvent(function() elseif name == 'column-style' then
self:setColumnStyle(value) addEvent(function()
end) self:setColumnStyle(value)
elseif name == 'row-style' then end)
addEvent(function() elseif name == 'row-style' then
self:setRowStyle(value) addEvent(function()
end) self:setRowStyle(value)
elseif name == 'header-column-style' then end)
addEvent(function() elseif name == 'header-column-style' then
self:setHeaderColumnStyle(value) addEvent(function()
end) self:setHeaderColumnStyle(value)
elseif name == 'header-row-style' then end)
addEvent(function() elseif name == 'header-row-style' then
self:setHeaderRowStyle(value) addEvent(function()
end) self:setHeaderRowStyle(value)
end)
end
end end
end end
end end
function UITable:setColumnWidth(width)
if self:hasHeader() then return end
self.columnWidth = width
end
function UITable:setDefaultColumnWidth(width)
self.defaultColumnWidth = width
end
-- Check if the table has a header
function UITable:hasHeader() function UITable:hasHeader()
return self.headerRow ~= nil return self.headerRow ~= nil
end end
-- Clear all rows
function UITable:clearData() function UITable:clearData()
if not self.dataSpace then if not self.dataSpace then
return return
@@ -78,16 +112,42 @@ function UITable:clearData()
self.rows = {} self.rows = {}
end end
function UITable:addHeaderRow(data) -- Set existing child as header
function UITable:setHeader(headerWidget)
self:removeHeader()
if self.dataSpace then
local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
self.dataSpace:applyStyle({ height = newHeight })
end
self.headerColumns = {}
self.columnWidth = {}
for colId, column in pairs(headerWidget:getChildren()) do
column.colId = colId
column.table = self
table.insert(self.columnWidth, column:getWidth())
table.insert(self.headerColumns, column)
end
self.headerRow = headerWidget
end
-- Create and add header from table data
function UITable:addHeader(data)
if not data or type(data) ~= 'table' then if not data or type(data) ~= 'table' then
g_logger.error('UITable:addHeaderRow - table columns must be provided in a table') g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
return return
end end
self:removeHeader()
-- build header columns -- build header columns
local columns = {} local columns = {}
for _, column in pairs(data) do for colId, column in pairs(data) do
local col = g_ui.createWidget(self.headerColumnBaseStyle) local col = g_ui.createWidget(self.headerColumnBaseStyle)
col.colId = colId
col.table = self
for type, value in pairs(column) do for type, value in pairs(column) do
if type == 'width' then if type == 'width' then
col:setWidth(value) col:setWidth(value)
@@ -104,26 +164,37 @@ function UITable:addHeaderRow(data)
-- create a new header -- create a new header
local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self) local headerRow = g_ui.createWidget(self.headerRowBaseStyle, self)
local newHeight = (self.dataSpace:getHeight()-headerRow:getHeight())-self.dataSpace:getMarginTop() local newHeight = self.dataSpace:getHeight()-headerRow:getHeight()-self.dataSpace:getMarginTop()
self.dataSpace:applyStyle({ height = newHeight }) self.dataSpace:applyStyle({ height = newHeight })
headerRow:setId(HEADER_ID) headerRow:setId('header')
self.headerColumns = {}
self.columnWidth = {}
for _, column in pairs(columns) do for _, column in pairs(columns) do
headerRow:addChild(column) headerRow:addChild(column)
self.columns[HEADER_ID] = column table.insert(self.columnWidth, column:getWidth())
table.insert(self.headerColumns, column)
end end
headerRow.onClick = function(headerRow) self:selectRow(headerRow) end
self.headerRow = headerRow self.headerRow = headerRow
return headerRow return headerRow
end end
function UITable:removeHeaderRow() -- Remove header
self.headerRow:destroy() function UITable:removeHeader()
self.headerRow = nil if self:hasHeader() then
if self.dataSpace then
local newHeight = self.dataSpace:getHeight()+self.headerRow:getHeight()+self.dataSpace:getMarginTop()
self.dataSpace:applyStyle({ height = newHeight })
end
self.headerColumns = {}
self.columnWidth = {}
self.headerRow:destroy()
self.headerRow = nil
end
end end
function UITable:addRow(data, ref, height) function UITable:addRow(data, height)
if not self.dataSpace then if not self.dataSpace then
g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.') g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
return return
@@ -134,41 +205,123 @@ function UITable:addRow(data, ref, height)
end end
local row = g_ui.createWidget(self.rowBaseStyle) local row = g_ui.createWidget(self.rowBaseStyle)
if ref then row.ref = ref end row.table = self
if height then row:setHeight(height) end if height then row:setHeight(height) end
local rowId = #self.rows local rowId = #self.rows + 1
row:setId('row'..(rowId < 1 and 1 or rowId)) row.rowId = rowId
row:setId('row'..rowId)
row:updateBackgroundColor()
for _, column in pairs(data) do self.columns[rowId] = {}
for colId, column in pairs(data) do
local col = g_ui.createWidget(self.columBaseStyle, row) local col = g_ui.createWidget(self.columBaseStyle, row)
for type, value in pairs(column) do if column.width then
if type == 'width' then col:setWidth(column.width)
col:setWidth(value) else
elseif type == 'height' then col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
col:setHeight(value)
elseif type == 'text' then
col:setText(value)
end
end end
self.columns[rowId] = col if column.height then
col:setHeight(column.height)
end
if column.text then
col:setText(column.text)
end
if column.sortvalue then
col.sortvalue = column.sortvalue
else
col.sortvalue = column.text or 0
end
table.insert(self.columns[rowId], col)
end end
row.onFocusChange = function(row, focused)
if focused then self:selectRow(row) end
end
self.dataSpace:addChild(row) self.dataSpace:addChild(row)
table.insert(self.rows, row) table.insert(self.rows, row)
if self.autoSort then
self:sort()
end
return row return row
end end
-- Update row indices and background color
function UITable:updateRows()
for rowId = 1, #self.rows do
local row = self.rows[rowId]
row.rowId = rowId
row:setId('row'..rowId)
row:updateBackgroundColor()
end
end
-- Removes the given row widget from the table
function UITable:removeRow(row) function UITable:removeRow(row)
if self.selectedRow == row then if self.selectedRow == row then
self:selectRow(nil) self:selectRow(nil)
end end
row.onClick = nil row.onClick = nil
table.removevalue(self.rows, row) row.table = nil
table.remove(self.columns, row.rowId)
table.remove(self.rows, row.rowId)
self.dataSpace:removeChild(row)
self:updateRows()
end
function UITable:toggleSorting(enabled)
self.autoSort = enabled
end
function UITable:setSorting(colId, sortType)
self.headerColumns[colId]:focus()
if sortType then
self.sortType = sortType
elseif self.sortColumn == colId then
if self.sortType == TABLE_SORTING_ASC then
self.sortType = TABLE_SORTING_DESC
else
self.sortType = TABLE_SORTING_ASC
end
else
self.sortType = TABLE_SORTING_ASC
end
self.sortColumn = colId
end
function UITable:sort()
if self.sortColumn <= 0 then
return
end
if self.sortType == TABLE_SORTING_ASC then
table.sort(self.rows, function(rowA, b)
return rowA:getChildByIndex(self.sortColumn).sortvalue < b:getChildByIndex(self.sortColumn).sortvalue
end)
else
table.sort(self.rows, function(rowA, b)
return rowA:getChildByIndex(self.sortColumn).sortvalue > b:getChildByIndex(self.sortColumn).sortvalue
end)
end
if self.dataSpace then
for _, child in pairs(self.dataSpace:getChildren()) do
self.dataSpace:removeChild(child)
end
end
self:updateRows()
self.columns = {}
for _, row in pairs(self.rows) do
if self.dataSpace then
self.dataSpace:addChild(row)
end
self.columns[row.rowId] = {}
for _, column in pairs(row:getChildren()) do
table.insert(self.columns[row.rowId], column)
end
end
end end
function UITable:selectRow(selectedRow) function UITable:selectRow(selectedRow)
@@ -189,21 +342,34 @@ function UITable:selectRow(selectedRow)
end end
function UITable:setTableData(tableData) function UITable:setTableData(tableData)
local headerHeight = 0
if self.headerRow then
headerHeight = self.headerRow:getHeight()
end
self.dataSpace = tableData self.dataSpace = tableData
self.dataSpace:applyStyle({ height = self:getHeight() }) self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() })
end end
function UITable:setRowStyle(style) function UITable:setRowStyle(style, dontUpdate)
self.rowBaseStyle = style self.rowBaseStyle = style
for _, row in pairs(self.rows) do
row:setStyle(style) if not dontUpdate then
for _, row in pairs(self.rows) do
row:setStyle(style)
end
end end
end end
function UITable:setColumnStyle(style) function UITable:setColumnStyle(style, dontUpdate)
self.columBaseStyle = style self.columBaseStyle = style
for _, col in pairs(self.columns) do
col:setStyle(style) if not dontUpdate then
for _, columns in pairs(self.columns) do
for _, col in pairs(columns) do
col:setStyle(style)
end
end
end end
end end
@@ -216,7 +382,51 @@ end
function UITable:setHeaderColumnStyle(style) function UITable:setHeaderColumnStyle(style)
self.headerColumnBaseStyle = style self.headerColumnBaseStyle = style
if table.haskey(HEADER_ID) then for _, col in pairs(self.headerColumns) do
self.columns[HEADER_ID]:setStyle(style) col:setStyle(style)
end
end
UITableRow = extends(UIWidget, "UITableRow")
function UITableRow:onFocusChange(focused)
if focused then
if self.table then self.table:selectRow(self) end
end
end
function UITableRow:onStyleApply(styleName, styleNode)
for name,value in pairs(styleNode) do
if name == 'even-background-color' then
self.evenBackgroundColor = value
elseif name == 'odd-background-color' then
self.oddBackgroundColor = value
end
end
end
function UITableRow:updateBackgroundColor()
self.backgroundColor = nil
local isEven = (self.rowId % 2 == 0)
if isEven and self.evenBackgroundColor then
self.backgroundColor = self.evenBackgroundColor
elseif not isEven and self.oddBackgroundColor then
self.backgroundColor = self.oddBackgroundColor
end
if self.backgroundColor then
self:mergeStyle({ ['background-color'] = self.backgroundColor })
end
end
UITableHeaderColumn = extends(UIButton, "UITableHeaderColumn")
function UITableHeaderColumn:onClick()
if self.table then
self.table:setSorting(self.colId)
self.table:sort()
end end
end end

View File

@@ -296,6 +296,16 @@ function numbertoboolean(number)
end end
end end
function protectedcall(func, ...)
local status, ret = pcall(func, ...)
if status then
return ret
end
perror(ret)
return false
end
function signalcall(param, ...) function signalcall(param, ...)
if type(param) == 'function' then if type(param) == 'function' then
local status, ret = pcall(param, ...) local status, ret = pcall(param, ...)
@@ -313,7 +323,7 @@ function signalcall(param, ...)
perror(ret) perror(ret)
end end
end end
elseif func ~= nil then elseif param ~= nil then
error('attempt to call a non function value') error('attempt to call a non function value')
end end
return false return false

View File

@@ -15,7 +15,8 @@ fightModeRadioGroup = nil
pvpModeRadioGroup = nil pvpModeRadioGroup = nil
function init() function init()
combatControlsButton = modules.client_topmenu.addRightGameToggleButton('combatControlsButton', tr('Combat Controls'), '/images/topbuttons/combatcontrols', toggle) combatControlsButton = modules.client_topmenu.addRightGameToggleButton('combatControlsButton',
tr('Combat Controls'), '/images/topbuttons/combatcontrols', toggle)
combatControlsButton:setOn(true) combatControlsButton:setOn(true)
combatControlsWindow = g_ui.loadUI('combatcontrols', modules.game_interface.getRightPanel()) combatControlsWindow = g_ui.loadUI('combatcontrols', modules.game_interface.getRightPanel())
combatControlsWindow:disableResize() combatControlsWindow:disableResize()

View File

@@ -3,7 +3,7 @@ SpeakTypesSettings = {
say = { speakType = MessageModes.Say, color = '#FFFF00' }, say = { speakType = MessageModes.Say, color = '#FFFF00' },
whisper = { speakType = MessageModes.Whisper, color = '#FFFF00' }, whisper = { speakType = MessageModes.Whisper, color = '#FFFF00' },
yell = { speakType = MessageModes.Yell, color = '#FFFF00' }, yell = { speakType = MessageModes.Yell, color = '#FFFF00' },
broadcast = { speakType = MessageModes.GamemasterPrivateFrom, color = '#F55E5E' }, broadcast = { speakType = MessageModes.GamemasterBroadcast, color = '#F55E5E' },
private = { speakType = MessageModes.PrivateTo, color = '#5FF7F7', private = true }, private = { speakType = MessageModes.PrivateTo, color = '#5FF7F7', private = true },
privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true }, privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true },
privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true }, privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true },
@@ -38,6 +38,7 @@ SpeakTypes = {
[MessageModes.RVRChannel] = SpeakTypesSettings.channelWhite, [MessageModes.RVRChannel] = SpeakTypesSettings.channelWhite,
[MessageModes.RVRContinue] = SpeakTypesSettings.rvrContinue, [MessageModes.RVRContinue] = SpeakTypesSettings.rvrContinue,
[MessageModes.RVRAnswer] = SpeakTypesSettings.rvrAnswerFrom, [MessageModes.RVRAnswer] = SpeakTypesSettings.rvrAnswerFrom,
[MessageModes.NpcFromStartBlock] = SpeakTypesSettings.privateNpcToPlayer,
-- ignored types -- ignored types
[MessageModes.Spell] = SpeakTypesSettings.none, [MessageModes.Spell] = SpeakTypesSettings.none,
@@ -105,7 +106,6 @@ function init()
consoleContentPanel = consolePanel:getChildById('consoleContentPanel') consoleContentPanel = consolePanel:getChildById('consoleContentPanel')
consoleTabBar = consolePanel:getChildById('consoleTabBar') consoleTabBar = consolePanel:getChildById('consoleTabBar')
consoleTabBar:setContentWidget(consoleContentPanel) consoleTabBar:setContentWidget(consoleContentPanel)
consoleTabBar:setTabSpacing(-1)
channels = {} channels = {}
consolePanel.onKeyPress = function(self, keyCode, keyboardModifiers) consolePanel.onKeyPress = function(self, keyCode, keyboardModifiers)
@@ -114,13 +114,10 @@ function init()
local tab = consoleTabBar:getCurrentTab() local tab = consoleTabBar:getCurrentTab()
if not tab then return false end if not tab then return false end
local consoleBuffer = tab.tabPanel:getChildById('consoleBuffer') local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText
if not consoleBuffer then return false end if not selection then return false end
local consoleLabel = consoleBuffer:getFocusedChild() g_window.setClipboardText(selection)
if not consoleLabel or not consoleLabel:hasSelection() then return false end
g_window.setClipboardText(consoleLabel:getSelection())
return true return true
end end
@@ -148,6 +145,27 @@ function init()
end end
end end
function clearSelection(consoleBuffer)
for _,label in pairs(consoleBuffer:getChildren()) do
label:clearSelection()
end
consoleBuffer.selectionText = nil
consoleBuffer.selection = nil
end
function selectAll(consoleBuffer)
clearSelection(consoleBuffer)
if consoleBuffer:getChildCount() > 0 then
local text = {}
for _,label in pairs(consoleBuffer:getChildren()) do
label:selectAll()
table.insert(text, label:getSelection())
end
consoleBuffer.selectionText = table.concat(text, '\n')
consoleBuffer.selection = { first = consoleBuffer:getChildIndex(consoleBuffer:getFirstChild()), last = consoleBuffer:getChildIndex(consoleBuffer:getLastChild()) }
end
end
function toggleChat() function toggleChat()
if consoleToggleChat:isChecked() then if consoleToggleChat:isChecked() then
disableChat() disableChat()
@@ -164,12 +182,18 @@ function enableChat()
g_keyboard.unbindKeyUp("Space") g_keyboard.unbindKeyUp("Space")
g_keyboard.unbindKeyUp("Enter") g_keyboard.unbindKeyUp("Enter")
g_keyboard.unbindKeyUp("Escape")
gameInterface.unbindWalkKey("W") gameInterface.unbindWalkKey("W")
gameInterface.unbindWalkKey("D") gameInterface.unbindWalkKey("D")
gameInterface.unbindWalkKey("S") gameInterface.unbindWalkKey("S")
gameInterface.unbindWalkKey("A") gameInterface.unbindWalkKey("A")
gameInterface.unbindWalkKey("E")
gameInterface.unbindWalkKey("Q")
gameInterface.unbindWalkKey("C")
gameInterface.unbindWalkKey("Z")
consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW")) consoleToggleChat:setTooltip(tr("Disable chat mode, allow to walk using ASDW"))
end end
@@ -187,12 +211,18 @@ function disableChat()
end end
g_keyboard.bindKeyUp("Space", quickFunc) g_keyboard.bindKeyUp("Space", quickFunc)
g_keyboard.bindKeyUp("Enter", quickFunc) g_keyboard.bindKeyUp("Enter", quickFunc)
g_keyboard.bindKeyUp("Escape", quickFunc)
gameInterface.bindWalkKey("W", North) gameInterface.bindWalkKey("W", North)
gameInterface.bindWalkKey("D", East) gameInterface.bindWalkKey("D", East)
gameInterface.bindWalkKey("S", South) gameInterface.bindWalkKey("S", South)
gameInterface.bindWalkKey("A", West) gameInterface.bindWalkKey("A", West)
gameInterface.bindWalkKey("E", NorthEast)
gameInterface.bindWalkKey("Q", NorthWest)
gameInterface.bindWalkKey("C", SouthEast)
gameInterface.bindWalkKey("Z", SouthWest)
consoleToggleChat:setTooltip(tr("Enable chat mode")) consoleToggleChat:setTooltip(tr("Enable chat mode"))
end end
@@ -233,7 +263,13 @@ function terminate()
violationWindow:destroy() violationWindow:destroy()
end end
consoleTabBar = nil
consoleContentPanel = nil
consoleToggleChat = nil
consoleTextEdit = nil
consolePanel:destroy() consolePanel:destroy()
consolePanel = nil
ownPrivateName = nil ownPrivateName = nil
Console = nil Console = nil
@@ -288,11 +324,14 @@ function clear()
channels = {} channels = {}
consoleTabBar:removeTab(defaultTab) consoleTabBar:removeTab(defaultTab)
defaultTab = nil
consoleTabBar:removeTab(serverTab) consoleTabBar:removeTab(serverTab)
serverTab = nil
local npcTab = consoleTabBar:getTab('NPCs') local npcTab = consoleTabBar:getTab('NPCs')
if npcTab then if npcTab then
consoleTabBar:removeTab(npcTab) consoleTabBar:removeTab(npcTab)
npcTab = nil
end end
if violationReportTab then if violationReportTab then
@@ -557,12 +596,102 @@ function addTabText(text, speaktype, tab, creatureName)
end end
label.name = creatureName label.name = creatureName
label.onMouseRelease = function (self, mousePos, mouseButton) consoleBuffer.onMouseRelease = function(self, mousePos, mouseButton)
processMessageMenu(mousePos, mouseButton, nil, nil, nil, tab)
end
label.onMouseRelease = function(self, mousePos, mouseButton)
processMessageMenu(mousePos, mouseButton, creatureName, text, self, tab) processMessageMenu(mousePos, mouseButton, creatureName, text, self, tab)
end end
label.onMousePress = function(self, mousePos, button)
if button == MouseLeftButton then clearSelection(consoleBuffer) end
end
label.onDragEnter = function(self, mousePos)
clearSelection(consoleBuffer)
return true
end
label.onDragLeave = function(self, droppedWidget, mousePos)
local text = {}
for selectionChild = consoleBuffer.selection.first, consoleBuffer.selection.last do
local label = self:getParent():getChildByIndex(selectionChild)
table.insert(text, label:getSelection())
end
consoleBuffer.selectionText = table.concat(text, '\n')
return true
end
label.onDragMove = function(self, mousePos, mouseMoved)
local parent = self:getParent()
local parentRect = parent:getPaddingRect()
local selfIndex = parent:getChildIndex(self)
local child = parent:getChildByPos(mousePos)
-- find bonding children
if not child then
if mousePos.y < self:getY() then
for index = selfIndex - 1, 1, -1 do
local label = parent:getChildByIndex(index)
if label:getY() + label:getHeight() > parentRect.y then
if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == 1 then
child = label
break
end
else
child = parent:getChildByIndex(index + 1)
break
end
end
elseif mousePos.y > self:getY() + self:getHeight() then
for index = selfIndex + 1, parent:getChildCount(), 1 do
local label = parent:getChildByIndex(index)
if label:getY() < parentRect.y + parentRect.height then
if (mousePos.y >= label:getY() and mousePos.y <= label:getY() + label:getHeight()) or index == parent:getChildCount() then
child = label
break
end
else
child = parent:getChildByIndex(index - 1)
break
end
end
else
child = self
end
end
if not child then return false end
local childIndex = parent:getChildIndex(child)
-- remove old selection
clearSelection(consoleBuffer)
-- update self selection
local textBegin = self:getTextPos(self:getLastClickPosition())
local textPos = self:getTextPos(mousePos)
self:setSelection(textBegin, textPos)
consoleBuffer.selection = { first = math.min(selfIndex, childIndex), last = math.max(selfIndex, childIndex) }
-- update siblings selection
if child ~= self then
for selectionChild = consoleBuffer.selection.first + 1, consoleBuffer.selection.last - 1 do
parent:getChildByIndex(selectionChild):selectAll()
end
local textPos = child:getTextPos(mousePos)
if childIndex > selfIndex then
child:setSelection(0, textPos)
else
child:setSelection(string.len(child:getText()), textPos)
end
end
return true
end
if consoleBuffer:getChildCount() > MAX_LINES then if consoleBuffer:getChildCount() > MAX_LINES then
consoleBuffer:getFirstChild():destroy() local child = consoleBuffer:getFirstChild()
clearSelection(consoleBuffer)
child:destroy()
end end
end end
@@ -578,6 +707,7 @@ end
function processChannelTabMenu(tab, mousePos, mouseButton) function processChannelTabMenu(tab, mousePos, mouseButton)
local menu = g_ui.createWidget('PopupMenu') local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
channelName = tab:getText() channelName = tab:getText()
if tab ~= defaultTab and tab ~= serverTab then if tab ~= defaultTab and tab ~= serverTab then
@@ -588,8 +718,28 @@ function processChannelTabMenu(tab, mousePos, mouseButton)
if consoleTabBar:getCurrentTab() == tab then if consoleTabBar:getCurrentTab() == tab then
menu:addOption(tr('Clear Messages'), function() clearChannel(consoleTabBar) end) menu:addOption(tr('Clear Messages'), function() clearChannel(consoleTabBar) end)
menu:addOption(tr('Save Messages'), function()
local panel = consoleTabBar:getTabPanel(tab)
local consoleBuffer = panel:getChildById('consoleBuffer')
local lines = {}
for _,label in pairs(consoleBuffer:getChildren()) do
table.insert(lines, label:getText())
end
local filename = channelName .. '.txt'
local filepath = '/' .. filename
-- extra information at the beginning
table.insert(lines, 1, os.date('\nChannel saved at %a %b %d %H:%M:%S %Y'))
if g_resources.fileExists(filepath) then
table.insert(lines, 1, protectedcall(g_resources.readFileContents, filepath) or '')
end
g_resources.writeFileContents(filepath, table.concat(lines, '\n'))
modules.game_textmessage.displayStatusMessage(tr('Channel appended to %s', filename))
end)
end end
--menu:addOption(tr('Save Messages'), function() --[[TODO]] end)
menu:display(mousePos) menu:display(mousePos)
end end
@@ -597,6 +747,7 @@ end
function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab) function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab)
if mouseButton == MouseRightButton then if mouseButton == MouseRightButton then
local menu = g_ui.createWidget('PopupMenu') local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
if creatureName and #creatureName > 0 then if creatureName and #creatureName > 0 then
if creatureName ~= g_game.getCharacterName() then if creatureName ~= g_game.getCharacterName() then
menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end) menu:addOption(tr('Message to ' .. creatureName), function () g_game.openPrivateChannel(creatureName) end)
@@ -622,12 +773,15 @@ function processMessageMenu(mousePos, mouseButton, creatureName, text, label, ta
menu:addOption(tr('Copy name'), function () g_window.setClipboardText(creatureName) end) menu:addOption(tr('Copy name'), function () g_window.setClipboardText(creatureName) end)
end end
if label:hasSelection() then local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText
menu:addOption(tr('Copy'), function() g_window.setClipboardText(label:getSelection()) end, '(Ctrl+C)') if selection and #selection > 0 then
menu:addOption(tr('Copy'), function() g_window.setClipboardText(selection) end, '(Ctrl+C)')
end end
menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end) if text then
menu:addOption(tr('Select all'), function() label:selectAll() end) menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end)
if tab.violations then end
menu:addOption(tr('Select all'), function() selectAll(tab.tabPanel:getChildById('consoleBuffer')) end)
if tab.violations and creatureName then
menu:addSeparator() menu:addSeparator()
menu:addOption(tr('Process') .. ' ' .. creatureName, function() processViolation(creatureName, text) end) menu:addOption(tr('Process') .. ' ' .. creatureName, function() processViolation(creatureName, text) end)
menu:addOption(tr('Remove') .. ' ' .. creatureName, function() g_game.closeRuleViolation(creatureName) end) menu:addOption(tr('Remove') .. ' ' .. creatureName, function() g_game.closeRuleViolation(creatureName) end)
@@ -687,7 +841,7 @@ function sendMessage(message, tab)
end end
-- player used whisper -- player used whisper
local chatCommandMessage = message:match("^%#[w|W] (.*)") chatCommandMessage = message:match("^%#[w|W] (.*)")
if chatCommandMessage ~= nil then if chatCommandMessage ~= nil then
chatCommandSayMode = 'whisper' chatCommandSayMode = 'whisper'
message = chatCommandMessage message = chatCommandMessage
@@ -695,12 +849,27 @@ function sendMessage(message, tab)
end end
-- player say -- player say
local chatCommandMessage = message:match("^%#[s|S] (.*)") chatCommandMessage = message:match("^%#[s|S] (.*)")
if chatCommandMessage ~= nil then if chatCommandMessage ~= nil then
chatCommandSayMode = 'say' chatCommandSayMode = 'say'
message = chatCommandMessage message = chatCommandMessage
channel = 0 channel = 0
end end
-- player red talk on channel
chatCommandMessage = message:match("^%#[c|C] (.*)")
if chatCommandMessage ~= nil then
chatCommandSayMode = 'channelRed'
message = chatCommandMessage
end
-- player broadcast
chatCommandMessage = message:match("^%#[b|B] (.*)")
if chatCommandMessage ~= nil then
chatCommandSayMode = 'broadcast'
message = chatCommandMessage
channel = 0
end
local findIni, findEnd, chatCommandInitial, chatCommandPrivate, chatCommandEnd, chatCommandMessage = message:find("([%*%@])(.+)([%*%@])(.*)") local findIni, findEnd, chatCommandInitial, chatCommandPrivate, chatCommandEnd, chatCommandMessage = message:find("([%*%@])(.+)([%*%@])(.*)")
if findIni ~= nil and findIni == 1 then -- player used private chat command if findIni ~= nil and findIni == 1 then -- player used private chat command
@@ -852,21 +1021,21 @@ function onTalk(name, level, mode, message, channelId, creaturePos)
if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or if (mode == MessageModes.Say or mode == MessageModes.Whisper or mode == MessageModes.Yell or
mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or
mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud) and mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud or
creaturePos then mode == MessageModes.NpcFromStartBlock) and creaturePos then
-- Remove curly braces from screen message local staticText = StaticText.create()
local staticMessage = message -- Remove curly braces from screen message
if mode == MessageModes.NpcFrom then local staticMessage = message
local highlightData = getHighlightedText(staticMessage) if mode == MessageModes.NpcFrom or mode == MessageModes.NpcFromStartBlock then
if #highlightData > 0 then local highlightData = getHighlightedText(staticMessage)
for i = 1, #highlightData / 3 do if #highlightData > 0 then
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] } for i = 1, #highlightData / 3 do
staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words) local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
end staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words)
end end
end end
staticText:setColor(speaktype.color)
local staticText = StaticText.create() end
staticText:addMessage(name, mode, staticMessage) staticText:addMessage(name, mode, staticMessage)
g_map.addThing(staticText, creaturePos, -1) g_map.addThing(staticText, creaturePos, -1)
end end

View File

@@ -10,6 +10,9 @@ ConsoleLabel < UITextEdit
change-cursor-image: false change-cursor-image: false
cursor-visible: false cursor-visible: false
editable: false editable: false
draggable: true
selectable: false
focusable: false
ConsolePhantomLabel < UILabel ConsolePhantomLabel < UILabel
font: verdana-11px-antialised font: verdana-11px-antialised
@@ -81,7 +84,8 @@ Panel
margin-left: 5 margin-left: 5
margin-top: 3 margin-top: 3
margin-right: 5 margin-right: 5
moveable: true tab-spacing: 2
movable: true
TabButton TabButton
id: nextChannelButton id: nextChannelButton

View File

@@ -27,6 +27,15 @@ function init()
onLoginAdvice = onLoginAdvice, onLoginAdvice = onLoginAdvice,
}, true) }, true)
-- Call load AFTER game window has been created and
-- resized to a stable state, otherwise the saved
-- settings can get overridden by false onGeometryChange
-- events
connect(g_app, {
onRun = load,
onExit = save
})
gameRootPanel = g_ui.displayUI('gameinterface') gameRootPanel = g_ui.displayUI('gameinterface')
gameRootPanel:hide() gameRootPanel:hide()
gameRootPanel:lower() gameRootPanel:lower()
@@ -49,7 +58,6 @@ function init()
setupViewMode(0) setupViewMode(0)
bindKeys() bindKeys()
load()
if g_game.isOnline() then if g_game.isOnline() then
show() show()
@@ -102,7 +110,6 @@ function unbindWalkKey(key)
end end
function terminate() function terminate()
save()
hide() hide()
hookedMenuOptions = {} hookedMenuOptions = {}
@@ -442,7 +449,10 @@ end
function createThingMenu(menuPosition, lookThing, useThing, creatureThing) function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
if not g_game.isOnline() then return end if not g_game.isOnline() then return end
local menu = g_ui.createWidget('PopupMenu') local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
local classic = modules.client_options.getOption('classicControl') local classic = modules.client_options.getOption('classicControl')
local shortcut = nil local shortcut = nil

View File

@@ -30,5 +30,6 @@ Module
- game_spelllist - game_spelllist
- game_cooldown - game_cooldown
- game_modaldialog - game_modaldialog
- game_unjustifiedpoints
@onLoad: init() @onLoad: init()
@onUnload: terminate() @onUnload: terminate()

View File

@@ -17,7 +17,10 @@ inventoryButton = nil
purseButton = nil purseButton = nil
function init() function init()
connect(LocalPlayer, { onInventoryChange = onInventoryChange }) connect(LocalPlayer, {
onInventoryChange = onInventoryChange,
onBlessingsChange = onBlessingsChange
})
connect(g_game, { onGameStart = refresh }) connect(g_game, { onGameStart = refresh })
g_keyboard.bindKeyDown('Ctrl+I', toggle) g_keyboard.bindKeyDown('Ctrl+I', toggle)
@@ -43,7 +46,10 @@ function init()
end end
function terminate() function terminate()
disconnect(LocalPlayer, { onInventoryChange = onInventoryChange }) disconnect(LocalPlayer, {
onInventoryChange = onInventoryChange,
onBlessingsChange = onBlessingsChange
})
disconnect(g_game, { onGameStart = refresh }) disconnect(g_game, { onGameStart = refresh })
g_keyboard.unbindKeyDown('Ctrl+I') g_keyboard.unbindKeyDown('Ctrl+I')
@@ -52,6 +58,15 @@ function terminate()
inventoryButton:destroy() inventoryButton:destroy()
end end
function toggleAdventurerStyle(hasBlessing)
for slot = InventorySlotFirst, InventorySlotLast do
local itemWidget = inventoryPanel:getChildById('slot' .. slot)
if itemWidget then
itemWidget:setOn(hasBlessing)
end
end
end
function refresh() function refresh()
local player = g_game.getLocalPlayer() local player = g_game.getLocalPlayer()
for i = InventorySlotFirst, InventorySlotPurse do for i = InventorySlotFirst, InventorySlotPurse do
@@ -60,6 +75,7 @@ function refresh()
else else
onInventoryChange(player, i, nil) onInventoryChange(player, i, nil)
end end
toggleAdventurerStyle(player and Bit.hasBit(player:getBlessings(), Blessings.Adventurer) or false)
end end
purseButton:setVisible(g_game.getFeature(GamePurseSlot)) purseButton:setVisible(g_game.getFeature(GamePurseSlot))
@@ -92,10 +108,17 @@ function onInventoryChange(player, slot, item, oldItem)
local itemWidget = inventoryPanel:getChildById('slot' .. slot) local itemWidget = inventoryPanel:getChildById('slot' .. slot)
if item then if item then
itemWidget:setStyle('Item') itemWidget:setStyle('InventoryItem')
itemWidget:setItem(item) itemWidget:setItem(item)
else else
itemWidget:setStyle(InventorySlotStyles[slot]) itemWidget:setStyle(InventorySlotStyles[slot])
itemWidget:setItem(nil) itemWidget:setItem(nil)
end end
end end
function onBlessingsChange(player, blessings, oldBlessings)
local hasAdventurerBlessing = Bit.hasBit(blessings, Blessings.Adventurer)
if hasAdventurerBlessing ~= Bit.hasBit(oldBlessings, Blessings.Adventurer) then
toggleAdventurerStyle(hasAdventurerBlessing)
end
end

View File

@@ -1,54 +1,76 @@
InventoryItem < Item InventoryItem < Item
$on:
image-source: /images/ui/item-blessed
HeadSlot < InventoryItem HeadSlot < InventoryItem
id: slot1 id: slot1
image-source: /images/game/slots/head image-source: /images/game/slots/head
&position: {x=65535, y=1, z=0} &position: {x=65535, y=1, z=0}
$on:
image-source: /images/game/slots/head-blessed
BodySlot < InventoryItem BodySlot < InventoryItem
id: slot4 id: slot4
image-source: /images/game/slots/body image-source: /images/game/slots/body
&position: {x=65535, y=4, z=0} &position: {x=65535, y=4, z=0}
$on:
image-source: /images/game/slots/body-blessed
LegSlot < InventoryItem LegSlot < InventoryItem
id: slot7 id: slot7
image-source: /images/game/slots/legs image-source: /images/game/slots/legs
&position: {x=65535, y=7, z=0} &position: {x=65535, y=7, z=0}
$on:
image-source: /images/game/slots/legs-blessed
FeetSlot < InventoryItem FeetSlot < InventoryItem
id: slot8 id: slot8
image-source: /images/game/slots/feet image-source: /images/game/slots/feet
&position: {x=65535, y=8, z=0} &position: {x=65535, y=8, z=0}
$on:
image-source: /images/game/slots/feet-blessed
NeckSlot < InventoryItem NeckSlot < InventoryItem
id: slot2 id: slot2
image-source: /images/game/slots/neck image-source: /images/game/slots/neck
&position: {x=65535, y=2, z=0} &position: {x=65535, y=2, z=0}
$on:
image-source: /images/game/slots/neck-blessed
LeftSlot < InventoryItem LeftSlot < InventoryItem
id: slot6 id: slot6
image-source: /images/game/slots/left-hand image-source: /images/game/slots/left-hand
&position: {x=65535, y=6, z=0} &position: {x=65535, y=6, z=0}
$on:
image-source: /images/game/slots/left-hand-blessed
FingerSlot < InventoryItem FingerSlot < InventoryItem
id: slot9 id: slot9
image-source: /images/game/slots/finger image-source: /images/game/slots/finger
&position: {x=65535, y=9, z=0} &position: {x=65535, y=9, z=0}
$on:
image-source: /images/game/slots/finger-blessed
BackSlot < InventoryItem BackSlot < InventoryItem
id: slot3 id: slot3
image-source: /images/game/slots/back image-source: /images/game/slots/back
&position: {x=65535, y=3, z=0} &position: {x=65535, y=3, z=0}
$on:
image-source: /images/game/slots/back-blessed
RightSlot < InventoryItem RightSlot < InventoryItem
id: slot5 id: slot5
image-source: /images/game/slots/right-hand image-source: /images/game/slots/right-hand
&position: {x=65535, y=5, z=0} &position: {x=65535, y=5, z=0}
$on:
image-source: /images/game/slots/right-hand-blessed
AmmoSlot < InventoryItem AmmoSlot < InventoryItem
id: slot10 id: slot10
image-source: /images/game/slots/ammo image-source: /images/game/slots/ammo
&position: {x=65535, y=10, z=0} &position: {x=65535, y=10, z=0}
$on:
image-source: /images/game/slots/ammo-blessed
PurseButton < Button PurseButton < Button
id: purseButton id: purseButton

View File

@@ -13,12 +13,8 @@
* Clean up the interface building * Clean up the interface building
- Add a new market interface file to handle building? - Add a new market interface file to handle building?
* Add offer table column ordering.
- Player Name, Amount, Total Price, Peice Price and Ends At
* Extend information features * Extend information features
- Hover over offers for purchase information (balance after transaction, etc) - Hover over offers for purchase information (balance after transaction, etc)
- Display out of trend market offers based on their previous statistics (like cipsoft does)
]] ]]
Market = {} Market = {}
@@ -42,6 +38,7 @@ currentOffersPanel = nil
offerHistoryPanel = nil offerHistoryPanel = nil
itemsPanel = nil itemsPanel = nil
selectedOffer = {} selectedOffer = {}
selectedMyOffer = {}
nameLabel = nil nameLabel = nil
feeLabel = nil feeLabel = nil
@@ -68,6 +65,11 @@ detailsTable = nil
buyStatsTable = nil buyStatsTable = nil
sellStatsTable = nil sellStatsTable = nil
buyCancelButton = nil
sellCancelButton = nil
buyMyOfferTable = nil
sellMyOfferTable = nil
offerExhaust = {} offerExhaust = {}
marketOffers = {} marketOffers = {}
marketItems = {} marketItems = {}
@@ -75,17 +77,10 @@ information = {}
currentItems = {} currentItems = {}
lastCreatedOffer = 0 lastCreatedOffer = 0
fee = 0 fee = 0
averagePrice = 0
loaded = false loaded = false
local offerTableHeader = {
{['text'] = 'Player Name', ['width'] = 100},
{['text'] = 'Amount', ['width'] = 60},
{['text'] = 'Total Price', ['width'] = 90},
{['text'] = 'Piece Price', ['width'] = 80},
{['text'] = 'Ends at', ['width'] = 120}
}
local function isItemValid(item, category, searchFilter) local function isItemValid(item, category, searchFilter)
if not item or not item.marketData then if not item or not item.marketData then
return false return false
@@ -110,7 +105,7 @@ local function isItemValid(item, category, searchFilter)
local filterDepot = filterButtons[MarketFilters.Depot]:isChecked() local filterDepot = filterButtons[MarketFilters.Depot]:isChecked()
if slotFilter then if slotFilter then
if slotFilter ~= 255 and item.ptr:getClothSlot() ~= slotFilter then if slotFilter ~= 255 and item.thingType:getClothSlot() ~= slotFilter then
return false return false
end end
end end
@@ -124,7 +119,7 @@ local function isItemValid(item, category, searchFilter)
return false return false
end end
end end
if filterDepot and Market.depotContains(item.ptr:getId()) <= 0 then if filterDepot and Market.getDepotCount(item.marketData.tradeAs) <= 0 then
return false return false
end end
if searchFilter then if searchFilter then
@@ -145,17 +140,24 @@ local function clearOffers()
sellOfferTable:clearData() sellOfferTable:clearData()
end end
local function clearMyOffers()
marketOffers[MarketAction.Buy] = {}
marketOffers[MarketAction.Sell] = {}
buyMyOfferTable:clearData()
sellMyOfferTable:clearData()
end
local function clearFilters() local function clearFilters()
for _, filter in pairs(filterButtons) do for _, filter in pairs(filterButtons) do
if filter and filter:isChecked() then if filter and filter:isChecked() ~= filter.default then
filter:setChecked(false) filter:setChecked(filter.default)
end end
end end
end end
local function clearFee() local function clearFee()
feeLabel:setText('') feeLabel:setText('')
fee = 0 fee = 20
end end
local function refreshTypeList() local function refreshTypeList()
@@ -163,13 +165,13 @@ local function refreshTypeList()
offerTypeList:addOption('Buy') offerTypeList:addOption('Buy')
if Market.isItemSelected() then if Market.isItemSelected() then
if Market.depotContains(selectedItem.item.ptr:getId()) > 0 then if Market.getDepotCount(selectedItem.item.marketData.tradeAs) > 0 then
offerTypeList:addOption('Sell') offerTypeList:addOption('Sell')
end end
end end
end end
local function addOffer(offer, type) local function addOffer(offer, offerType)
if not offer then if not offer then
return false return false
end end
@@ -178,27 +180,85 @@ local function addOffer(offer, type)
local amount = offer:getAmount() local amount = offer:getAmount()
local price = offer:getPrice() local price = offer:getPrice()
local timestamp = offer:getTimeStamp() local timestamp = offer:getTimeStamp()
local itemName = offer:getItem():getMarketData().name
buyOfferTable:toggleSorting(false)
sellOfferTable:toggleSorting(false)
buyMyOfferTable:toggleSorting(false)
sellMyOfferTable:toggleSorting(false)
if amount < 1 then return false end if amount < 1 then return false end
if type == MarketAction.Buy then if offerType == MarketAction.Buy then
local data = { if offer.warn then
{['text'] = player, ['width'] = 100}, buyOfferTable:setColumnStyle('OfferTableWarningColumn', true)
{['text'] = amount, ['width'] = 60}, end
{['text'] = price*amount, ['width'] = 90},
{['text'] = price, ['width'] = 80}, local row = nil
{['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120} if offer.var == MarketRequest.MyOffers then
} row = buyMyOfferTable:addRow({
buyOfferTable:addRow(data, id) {text = itemName},
{text = price*amount},
{text = price},
{text = amount},
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
})
else
row = buyOfferTable:addRow({
{text = player},
{text = amount},
{text = price*amount},
{text = price},
{text = string.gsub(os.date('%c', timestamp), " ", " ")}
})
end
row.ref = id
if offer.warn then
row:setTooltip(tr('This offer is 25%% below the average market price'))
buyOfferTable:setColumnStyle('OfferTableColumn', true)
end
else else
local data = { if offer.warn then
{['text'] = player, ['width'] = 100}, sellOfferTable:setColumnStyle('OfferTableWarningColumn', true)
{['text'] = amount, ['width'] = 60}, end
{['text'] = price*amount, ['width'] = 90},
{['text'] = price, ['width'] = 80}, local row = nil
{['text'] = string.gsub(os.date('%c', timestamp), " ", " "), ['width'] = 120} if offer.var == MarketRequest.MyOffers then
} row = sellMyOfferTable:addRow({
sellOfferTable:addRow(data, id) {text = itemName},
{text = price*amount},
{text = price},
{text = amount},
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
})
else
row = sellOfferTable:addRow({
{text = player},
{text = amount},
{text = price*amount},
{text = price},
{text = string.gsub(os.date('%c', timestamp), " ", " "), sortvalue = timestamp}
})
end
row.ref = id
if offer.warn then
row:setTooltip(tr('This offer is 25%% above the average market price'))
sellOfferTable:setColumnStyle('OfferTableColumn', true)
end
end end
buyOfferTable:toggleSorting(false)
sellOfferTable:toggleSorting(false)
buyOfferTable:sort()
sellOfferTable:sort()
buyMyOfferTable:toggleSorting(false)
sellMyOfferTable:toggleSorting(false)
buyMyOfferTable:sort()
sellMyOfferTable:sort()
return true return true
end end
@@ -206,12 +266,17 @@ local function mergeOffer(offer)
if not offer then if not offer then
return false return false
end end
local id = offer:getId() local id = offer:getId()
local type = offer:getType() local offerType = offer:getType()
local amount = offer:getAmount() local amount = offer:getAmount()
local replaced = false local replaced = false
if type == MarketAction.Buy then if offerType == MarketAction.Buy then
if averagePrice > 0 then
offer.warn = offer:getPrice() <= averagePrice - math.floor(averagePrice / 4)
end
for i = 1, #marketOffers[MarketAction.Buy] do for i = 1, #marketOffers[MarketAction.Buy] do
local o = marketOffers[MarketAction.Buy][i] local o = marketOffers[MarketAction.Buy][i]
-- replace existing offer -- replace existing offer
@@ -224,6 +289,10 @@ local function mergeOffer(offer)
table.insert(marketOffers[MarketAction.Buy], offer) table.insert(marketOffers[MarketAction.Buy], offer)
end end
else else
if averagePrice > 0 then
offer.warn = offer:getPrice() >= averagePrice + math.floor(averagePrice / 4)
end
for i = 1, #marketOffers[MarketAction.Sell] do for i = 1, #marketOffers[MarketAction.Sell] do
local o = marketOffers[MarketAction.Sell][i] local o = marketOffers[MarketAction.Sell][i]
-- replace existing offer -- replace existing offer
@@ -248,13 +317,21 @@ local function updateOffers(offers)
selectedOffer[MarketAction.Buy] = nil selectedOffer[MarketAction.Buy] = nil
selectedOffer[MarketAction.Sell] = nil selectedOffer[MarketAction.Sell] = nil
selectedMyOffer[MarketAction.Buy] = nil
selectedMyOffer[MarketAction.Sell] = nil
-- clear existing offer data -- clear existing offer data
buyOfferTable:clearData() buyOfferTable:clearData()
buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
sellOfferTable:clearData() sellOfferTable:clearData()
sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
sellButton:setEnabled(false) sellButton:setEnabled(false)
buyButton:setEnabled(false) buyButton:setEnabled(false)
buyCancelButton:setEnabled(false)
sellCancelButton:setEnabled(false)
for _, offer in pairs(offers) do for _, offer in pairs(offers) do
mergeOffer(offer) mergeOffer(offer)
end end
@@ -274,8 +351,8 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
detailsTable:clearData() detailsTable:clearData()
for k, desc in pairs(descriptions) do for k, desc in pairs(descriptions) do
local columns = { local columns = {
{['text'] = getMarketDescriptionName(desc[1])..':'}, {text = getMarketDescriptionName(desc[1])..':'},
{['text'] = desc[2], ['width'] = 330} {text = desc[2]}
} }
detailsTable:addRow(columns) detailsTable:addRow(columns)
end end
@@ -283,11 +360,13 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
-- update sale item statistics -- update sale item statistics
sellStatsTable:clearData() sellStatsTable:clearData()
if table.empty(saleStats) then if table.empty(saleStats) then
sellStatsTable:addRow({{['text'] = 'No information'}}) sellStatsTable:addRow({{text = 'No information'}})
else else
local offerAmount = 0
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
for _, stat in pairs(saleStats) do for _, stat in pairs(saleStats) do
if not stat:isNull() then if not stat:isNull() then
offerAmount = offerAmount + 1
transactions = transactions + stat:getTransactions() transactions = transactions + stat:getTransactions()
totalPrice = totalPrice + stat:getTotalPrice() totalPrice = totalPrice + stat:getTotalPrice()
local newHigh = stat:getHighestPrice() local newHigh = stat:getHighestPrice()
@@ -301,28 +380,30 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
end end
end end
end end
sellStatsTable:addRow({{['text'] = 'Total Transations:'},
{['text'] = transactions, ['width'] = 270}})
sellStatsTable:addRow({{['text'] = 'Highest Price:'}, if offerAmount >= 5 and transactions >= 10 then
{['text'] = highestPrice, ['width'] = 270}}) averagePrice = math.round(totalPrice / transactions)
if totalPrice > 0 and transactions > 0 then
sellStatsTable:addRow({{['text'] = 'Average Price:'},
{['text'] = math.floor(totalPrice/transactions), ['width'] = 270}})
else else
sellStatsTable:addRow({{['text'] = 'Average Price:'}, averagePrice = 0
{['text'] = 0, ['width'] = 270}})
end end
sellStatsTable:addRow({{['text'] = 'Lowest Price:'}, sellStatsTable:addRow({{text = 'Total Transations:'}, {text = transactions}})
{['text'] = lowestPrice, ['width'] = 270}}) sellStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
if totalPrice > 0 and transactions > 0 then
sellStatsTable:addRow({{text = 'Average Price:'},
{text = math.floor(totalPrice/transactions)}})
else
sellStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
end
sellStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
end end
-- update buy item statistics -- update buy item statistics
buyStatsTable:clearData() buyStatsTable:clearData()
if table.empty(purchaseStats) then if table.empty(purchaseStats) then
buyStatsTable:addRow({{['text'] = 'No information'}}) buyStatsTable:addRow({{text = 'No information'}})
else else
local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0 local transactions, totalPrice, highestPrice, lowestPrice = 0, 0, 0, 0
for _, stat in pairs(purchaseStats) do for _, stat in pairs(purchaseStats) do
@@ -340,22 +421,18 @@ local function updateDetails(itemId, descriptions, purchaseStats, saleStats)
end end
end end
end end
buyStatsTable:addRow({{['text'] = 'Total Transations:'},
{['text'] = transactions, ['width'] = 270}})
buyStatsTable:addRow({{['text'] = 'Highest Price:'}, buyStatsTable:addRow({{text = 'Total Transations:'},{text = transactions}})
{['text'] = highestPrice, ['width'] = 270}}) buyStatsTable:addRow({{text = 'Highest Price:'}, {text = highestPrice}})
if totalPrice > 0 and transactions > 0 then if totalPrice > 0 and transactions > 0 then
buyStatsTable:addRow({{['text'] = 'Average Price:'}, buyStatsTable:addRow({{text = 'Average Price:'},
{['text'] = math.floor(totalPrice/transactions), ['width'] = 270}}) {text = math.floor(totalPrice/transactions)}})
else else
buyStatsTable:addRow({{['text'] = 'Average Price:'}, buyStatsTable:addRow({{text = 'Average Price:'}, {text = 0}})
{['text'] = 0, ['width'] = 270}})
end end
buyStatsTable:addRow({{['text'] = 'Lowest Price:'}, buyStatsTable:addRow({{text = 'Lowest Price:'}, {text = lowestPrice}})
{['text'] = lowestPrice, ['width'] = 270}})
end end
end end
@@ -365,12 +442,12 @@ local function updateSelectedItem(widget)
Market.resetCreateOffer() Market.resetCreateOffer()
if Market.isItemSelected() then if Market.isItemSelected() then
selectedItem:setItem(selectedItem.item.ptr) selectedItem:setItem(selectedItem.item.displayItem)
nameLabel:setText(selectedItem.item.marketData.name) nameLabel:setText(selectedItem.item.marketData.name)
clearOffers() clearOffers()
Market.enableCreateOffer(true) -- update offer types Market.enableCreateOffer(true) -- update offer types
MarketProtocol.sendMarketBrowse(selectedItem.item.ptr:getId()) -- send browsed msg MarketProtocol.sendMarketBrowse(selectedItem.item.marketData.tradeAs) -- send browsed msg
else else
Market.clearSelectedItem() Market.clearSelectedItem()
end end
@@ -400,55 +477,75 @@ local function updateFee(price, amount)
feeLabel:resizeToText() feeLabel:resizeToText()
end end
local function openAmountWindow(callback, type, actionText) local function destroyAmountWindow()
local actionText = actionText or '' if amountWindow then
if not Market.isOfferSelected(type) then amountWindow:destroy()
amountWindow = nil
end
end
local function cancelMyOffer(actionType)
local offer = selectedMyOffer[actionType]
MarketProtocol.sendMarketCancelOffer(offer:getTimeStamp(), offer:getCounter())
Market.refreshMyOffers()
end
local function openAmountWindow(callback, actionType, actionText)
if not Market.isOfferSelected(actionType) then
return return
end end
amountWindow = g_ui.createWidget('AmountWindow', rootWidget) amountWindow = g_ui.createWidget('AmountWindow', rootWidget)
amountWindow:lock() amountWindow:lock()
local item = selectedOffer[type]:getItem()
local max = selectedOffer[type]:getAmount(item:getId()) local offer = selectedOffer[actionType]
if type == MarketAction.Sell then local item = offer:getItem()
local depot = Market.depotContains(item:getId())
if max > depot then local maximum = offer:getAmount()
max = depot if actionType == MarketAction.Sell then
local depot = Market.getDepotCount(item:getId())
if maximum > depot then
maximum = depot
end end
else
maximum = math.min(maximum, math.floor(information.balance / offer:getPrice()))
end
if item:isStackable() then
maximum = math.min(maximum, MarketMaxAmountStackable)
else
maximum = math.min(maximum, MarketMaxAmount)
end end
local itembox = amountWindow:getChildById('item') local itembox = amountWindow:getChildById('item')
itembox:setItemId(item:getId()) itembox:setItemId(item:getId())
itembox:setText(1)
local scrollbar = amountWindow:getChildById('amountScrollBar') local scrollbar = amountWindow:getChildById('amountScrollBar')
scrollbar:setText(tostring(selectedOffer[type]:getPrice())..'gp') scrollbar:setText(offer:getPrice()..'gp')
scrollbar:setMaximum(max)
scrollbar:setMinimum(1)
scrollbar:setValue(1)
scrollbar.onValueChange = function(widget, value) scrollbar.onValueChange = function(widget, value)
widget:setText(tostring(value*selectedOffer[type]:getPrice())..'gp') widget:setText((value*offer:getPrice())..'gp')
itembox:setText(tostring(value)) itembox:setText(value)
end end
scrollbar:setRange(1, maximum)
scrollbar:setValue(1)
local okButton = amountWindow:getChildById('buttonOk') local okButton = amountWindow:getChildById('buttonOk')
if actionText ~= '' then if actionText then
okButton:setText(actionText) okButton:setText(actionText)
end end
local okFunc = function() local okFunc = function()
local counter = selectedOffer[type]:getCounter() local counter = offer:getCounter()
local timestamp = selectedOffer[type]:getTimeStamp() local timestamp = offer:getTimeStamp()
callback(scrollbar:getValue(), timestamp, counter) callback(scrollbar:getValue(), timestamp, counter)
okButton:getParent():destroy() destroyAmountWindow()
amountWindow = nil
end end
local cancelButton = amountWindow:getChildById('buttonCancel') local cancelButton = amountWindow:getChildById('buttonCancel')
local cancelFunc = function() local cancelFunc = function()
cancelButton:getParent():destroy() destroyAmountWindow()
amountWindow = nil
end end
amountWindow.onEnter = okFunc amountWindow.onEnter = okFunc
@@ -492,7 +589,7 @@ local function onSelectBuyOffer(table, selectedRow, previousSelectedRow)
for _, offer in pairs(marketOffers[MarketAction.Buy]) do for _, offer in pairs(marketOffers[MarketAction.Buy]) do
if offer:isEqual(selectedRow.ref) then if offer:isEqual(selectedRow.ref) then
selectedOffer[MarketAction.Sell] = offer selectedOffer[MarketAction.Sell] = offer
if Market.depotContains(offer:getItem():getId()) > 0 then if Market.getDepotCount(offer:getItem():getId()) > 0 then
sellButton:setEnabled(true) sellButton:setEnabled(true)
else else
sellButton:setEnabled(false) sellButton:setEnabled(false)
@@ -501,6 +598,24 @@ local function onSelectBuyOffer(table, selectedRow, previousSelectedRow)
end end
end end
local function onSelectMyBuyOffer(table, selectedRow, previousSelectedRow)
for _, offer in pairs(marketOffers[MarketAction.Buy]) do
if offer:isEqual(selectedRow.ref) then
selectedMyOffer[MarketAction.Buy] = offer
buyCancelButton:setEnabled(true)
end
end
end
local function onSelectMySellOffer(table, selectedRow, previousSelectedRow)
for _, offer in pairs(marketOffers[MarketAction.Sell]) do
if offer:isEqual(selectedRow.ref) then
selectedMyOffer[MarketAction.Sell] = offer
sellCancelButton:setEnabled(true)
end
end
end
local function onChangeCategory(combobox, option) local function onChangeCategory(combobox, option)
local id = getMarketCategoryId(option) local id = getMarketCategoryId(option)
if id == MarketCategory.MetaWeapons then if id == MarketCategory.MetaWeapons then
@@ -535,74 +650,84 @@ local function onChangeSlotFilter(combobox, option)
end end
local function onChangeOfferType(combobox, option) local function onChangeOfferType(combobox, option)
local id = selectedItem.item.ptr:getId() local item = selectedItem.item
local maximum = item.thingType:isStackable() and MarketMaxAmountStackable or MarketMaxAmount
if option == 'Sell' then if option == 'Sell' then
local max = Market.depotContains(id) maximum = math.min(maximum, Market.getDepotCount(item.marketData.tradeAs))
amountEdit:setMaximum(max) amountEdit:setMaximum(maximum)
else else
amountEdit:setMaximum(999999) amountEdit:setMaximum(maximum)
end end
end end
local function onTotalPriceChange() local function onTotalPriceChange()
local totalPrice = totalPriceEdit:getValue()
local piecePrice = piecePriceEdit:getValue()
local amount = amountEdit:getValue() local amount = amountEdit:getValue()
local totalPrice = totalPriceEdit:getValue()
local piecePrice = math.floor(totalPrice/amount)
piecePriceEdit:setValue(math.floor(totalPrice/amount)) piecePriceEdit:setValue(piecePrice, true)
if Market.isItemSelected() then if Market.isItemSelected() then
updateFee(totalPrice, amount) updateFee(piecePrice, amount)
end end
end end
local function onPiecePriceChange() local function onPiecePriceChange()
local amount = amountEdit:getValue()
local totalPrice = totalPriceEdit:getValue() local totalPrice = totalPriceEdit:getValue()
local piecePrice = piecePriceEdit:getValue() local piecePrice = piecePriceEdit:getValue()
local amount = amountEdit:getValue()
totalPriceEdit:setValue(piecePrice*amount) totalPriceEdit:setValue(piecePrice*amount, true)
if Market.isItemSelected() then if Market.isItemSelected() then
updateFee(totalPrice, amount) updateFee(piecePrice, amount)
end end
end end
local function onAmountChange() local function onAmountChange()
local totalPrice = totalPriceEdit:getValue()
local piecePrice = piecePriceEdit:getValue()
local amount = amountEdit:getValue() local amount = amountEdit:getValue()
local piecePrice = piecePriceEdit:getValue()
local totalPrice = piecePrice * amount
piecePriceEdit:setValue(math.floor(totalPrice/amount)) totalPriceEdit:setValue(piecePrice*amount, true)
totalPriceEdit:setValue(piecePrice*amount)
if Market.isItemSelected() then if Market.isItemSelected() then
updateFee(totalPrice, amount) updateFee(piecePrice, amount)
end end
end end
local function initMarketItems(category) local function onMarketMessage(messageMode, message)
Market.displayMessage(message)
end
local function initMarketItems()
for c = MarketCategory.First, MarketCategory.Last do for c = MarketCategory.First, MarketCategory.Last do
marketItems[c] = {} marketItems[c] = {}
end end
-- save a list of items which are already added
local itemSet = {}
-- populate all market items -- populate all market items
local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0) local types = g_things.findThingTypeByAttr(ThingAttrMarket, 0)
for i = 1, #types do for i = 1, #types do
local t = types[i] local itemType = types[i]
local newItem = Item.create(t:getId()) local item = Item.create(itemType:getId())
if newItem then if item then
local marketData = t:getMarketData() local marketData = itemType:getMarketData()
if not table.empty(marketData) then if not table.empty(marketData) and not itemSet[marketData.tradeAs] then
if marketData.category == category or category == MarketCategory.All then -- Some items use a different sprite in Market
item:setId(marketData.showAs)
-- create new item block -- create new marketItem block
local item = { local marketItem = {
ptr = newItem, displayItem = item,
thingType = itemType,
marketData = marketData marketData = marketData
} }
-- add new market item -- add new market item
table.insert(marketItems[marketData.category], item) table.insert(marketItems[marketData.category], marketItem)
end itemSet[marketData.tradeAs] = true
end end
end end
end end
@@ -624,8 +749,10 @@ local function initInterface()
browsePanel = g_ui.loadUI('ui/marketoffers/browse') browsePanel = g_ui.loadUI('ui/marketoffers/browse')
selectionTabBar:addTab(tr('Browse'), browsePanel) selectionTabBar:addTab(tr('Browse'), browsePanel)
overviewPanel = g_ui.loadUI('ui/marketoffers/overview') -- Currently not used
selectionTabBar:addTab(tr('Overview'), overviewPanel) -- "Reserved for more functionality later"
--overviewPanel = g_ui.loadUI('ui/marketoffers/overview')
--selectionTabBar:addTab(tr('Overview'), overviewPanel)
displaysTabBar = marketOffersPanel:getChildById('rightTabBar') displaysTabBar = marketOffersPanel:getChildById('rightTabBar')
displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent')) displaysTabBar:setContentWidget(marketOffersPanel:getChildById('rightTabContent'))
@@ -665,7 +792,6 @@ local function initInterface()
-- setup selected item -- setup selected item
nameLabel = marketOffersPanel:getChildById('nameLabel') nameLabel = marketOffersPanel:getChildById('nameLabel')
selectedItem = marketOffersPanel:getChildById('selectedItem') selectedItem = marketOffersPanel:getChildById('selectedItem')
selectedItem.item = {}
-- setup create new offer -- setup create new offer
totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit') totalPriceEdit = marketOffersPanel:getChildById('totalPriceEdit')
@@ -690,6 +816,14 @@ local function initInterface()
filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot') filterButtons[MarketFilters.Depot] = browsePanel:getChildById('filterDepot')
filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll') filterButtons[MarketFilters.SearchAll] = browsePanel:getChildById('filterSearchAll')
-- set filter default values
clearFilters()
-- hook filters
for _, filter in pairs(filterButtons) do
filter.onCheckChange = Market.updateCurrentItems
end
searchEdit = browsePanel:getChildById('searchEdit') searchEdit = browsePanel:getChildById('searchEdit')
categoryList = browsePanel:getChildById('categoryComboBox') categoryList = browsePanel:getChildById('categoryComboBox')
subCategoryList = browsePanel:getChildById('subCategoryComboBox') subCategoryList = browsePanel:getChildById('subCategoryComboBox')
@@ -722,6 +856,29 @@ local function initInterface()
sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable') sellStatsTable = itemStatsPanel:recursiveGetChildById('sellStatsTable')
buyOfferTable.onSelectionChange = onSelectBuyOffer buyOfferTable.onSelectionChange = onSelectBuyOffer
sellOfferTable.onSelectionChange = onSelectSellOffer sellOfferTable.onSelectionChange = onSelectSellOffer
-- setup my offers
buyMyOfferTable = currentOffersPanel:recursiveGetChildById('myBuyingTable')
sellMyOfferTable = currentOffersPanel:recursiveGetChildById('mySellingTable')
buyMyOfferTable.onSelectionChange = onSelectMyBuyOffer
sellMyOfferTable.onSelectionChange = onSelectMySellOffer
buyCancelButton = currentOffersPanel:getChildById('buyCancelButton')
buyCancelButton.onClick = function() cancelMyOffer(MarketAction.Buy) end
sellCancelButton = currentOffersPanel:getChildById('sellCancelButton')
sellCancelButton.onClick = function() cancelMyOffer(MarketAction.Sell) end
buyStatsTable:setColumnWidth({120, 270})
sellStatsTable:setColumnWidth({120, 270})
detailsTable:setColumnWidth({80, 330})
buyOfferTable:setSorting(4, TABLE_SORTING_DESC)
sellOfferTable:setSorting(4, TABLE_SORTING_ASC)
buyMyOfferTable:setSorting(3, TABLE_SORTING_DESC)
sellMyOfferTable:setSorting(3, TABLE_SORTING_DESC)
end end
function init() function init()
@@ -734,6 +891,8 @@ function init()
offerExhaust[MarketAction.Sell] = 10 offerExhaust[MarketAction.Sell] = 10
offerExhaust[MarketAction.Buy] = 20 offerExhaust[MarketAction.Buy] = 20
registerMessageMode(MessageModes.Market, onMarketMessage)
protocol.initProtocol() protocol.initProtocol()
connect(g_game, { onGameEnd = Market.reset }) connect(g_game, { onGameEnd = Market.reset })
connect(g_game, { onGameEnd = Market.close }) connect(g_game, { onGameEnd = Market.close })
@@ -744,10 +903,15 @@ function init()
end end
function terminate() function terminate()
Market.close()
unregisterMessageMode(MessageModes.Market, onMarketMessage)
protocol.terminateProtocol() protocol.terminateProtocol()
disconnect(g_game, { onGameEnd = Market.reset }) disconnect(g_game, { onGameEnd = Market.reset })
disconnect(g_game, { onGameEnd = Market.close }) disconnect(g_game, { onGameEnd = Market.close })
destroyAmountWindow()
marketWindow:destroy() marketWindow:destroy()
Market = nil Market = nil
@@ -758,14 +922,22 @@ function Market.reset()
categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First)) categoryList:setCurrentOption(getMarketCategoryName(MarketCategory.First))
searchEdit:setText('') searchEdit:setText('')
clearFilters() clearFilters()
clearMyOffers()
if not table.empty(information) then if not table.empty(information) then
Market.updateCurrentItems() Market.updateCurrentItems()
end end
end end
function Market.displayMessage(message)
if marketWindow:isHidden() then return end
local infoBox = displayInfoBox(tr('Market Error'), message)
infoBox:lock()
end
function Market.clearSelectedItem() function Market.clearSelectedItem()
if Market.isItemSelected() then if Market.isItemSelected() then
Market.resetCreateOffer() Market.resetCreateOffer(true)
offerTypeList:clearOptions() offerTypeList:clearOptions()
offerTypeList:setText('Please Select') offerTypeList:setText('Please Select')
offerTypeList:setEnabled(false) offerTypeList:setEnabled(false)
@@ -787,22 +959,15 @@ function Market.clearSelectedItem()
end end
function Market.isItemSelected() function Market.isItemSelected()
return selectedItem and not table.empty(selectedItem.item) and selectedItem.item.ptr return selectedItem and selectedItem.item
end end
function Market.isOfferSelected(type) function Market.isOfferSelected(type)
return selectedOffer[type] and not selectedOffer[type]:isNull() return selectedOffer[type] and not selectedOffer[type]:isNull()
end end
function Market.depotContains(itemId) function Market.getDepotCount(itemId)
local count = 0 return information.depotItems[itemId] or 0
for i = 1, #information.depotItems do
local item = information.depotItems[i]
if item and item.ptr:getId() == itemId then
count = count + item.ptr:getCount()
end
end
return count
end end
function Market.enableCreateOffer(enable) function Market.enableCreateOffer(enable)
@@ -825,6 +990,8 @@ function Market.close(notify)
if not marketWindow:isHidden() then if not marketWindow:isHidden() then
marketWindow:hide() marketWindow:hide()
marketWindow:unlock() marketWindow:unlock()
Market.clearSelectedItem()
Market.reset()
if notify then if notify then
MarketProtocol.sendMarketLeave() MarketProtocol.sendMarketLeave()
end end
@@ -847,12 +1014,17 @@ function Market.updateCurrentItems()
Market.loadMarketItems(id) Market.loadMarketItems(id)
end end
function Market.resetCreateOffer() function Market.resetCreateOffer(resetFee)
piecePriceEdit:setValue(1) piecePriceEdit:setValue(1)
totalPriceEdit:setValue(1) totalPriceEdit:setValue(1)
amountEdit:setValue(1) amountEdit:setValue(1)
refreshTypeList() refreshTypeList()
clearFee()
if resetFee then
clearFee()
else
updateFee(0, 0)
end
end end
function Market.refreshItemsWidget(selectItem) function Market.refreshItemsWidget(selectItem)
@@ -877,15 +1049,15 @@ function Market.refreshItemsWidget(selectItem)
itemBox.onCheckChange = Market.onItemBoxChecked itemBox.onCheckChange = Market.onItemBoxChecked
itemBox.item = item itemBox.item = item
if selectItem > 0 and item.ptr:getId() == selectItem then if selectItem > 0 and item.marketData.tradeAs == selectItem then
select = itemBox select = itemBox
selectItem = 0
end end
local itemWidget = itemBox:getChildById('item') local itemWidget = itemBox:getChildById('item')
item.ptr:setCount(1) -- reset item count for image itemWidget:setItem(item.displayItem)
itemWidget:setItem(item.ptr)
local amount = Market.depotContains(item.ptr:getId()) local amount = Market.getDepotCount(item.marketData.tradeAs)
if amount > 0 then if amount > 0 then
itemWidget:setText(amount) itemWidget:setText(amount)
itemBox:setTooltip('You have '.. amount ..' in your depot.') itemBox:setTooltip('You have '.. amount ..' in your depot.')
@@ -893,8 +1065,9 @@ function Market.refreshItemsWidget(selectItem)
radioItemSet:addWidget(itemBox) radioItemSet:addWidget(itemBox)
end end
if select then if select then
select:setChecked(true) radioItemSet:selectWidget(select, false)
end end
layout:enableUpdates() layout:enableUpdates()
@@ -904,9 +1077,16 @@ end
function Market.refreshOffers() function Market.refreshOffers()
if Market.isItemSelected() then if Market.isItemSelected() then
Market.onItemBoxChecked(selectedItem.ref) Market.onItemBoxChecked(selectedItem.ref)
else
Market.refreshMyOffers()
end end
end end
function Market.refreshMyOffers()
clearMyOffers()
MarketProtocol.sendMarketBrowseMyOffers()
end
function Market.loadMarketItems(category) function Market.loadMarketItems(category)
clearItems() clearItems()
@@ -925,6 +1105,7 @@ function Market.loadMarketItems(category)
for i = 1, #marketItems[category] do for i = 1, #marketItems[category] do
local item = marketItems[category][i] local item = marketItems[category][i]
if isItemValid(item, category, searchFilter) then if isItemValid(item, category, searchFilter) then
table.insert(currentItems, item) table.insert(currentItems, item)
end end
end end
@@ -942,56 +1123,6 @@ function Market.loadMarketItems(category)
Market.refreshItemsWidget() Market.refreshItemsWidget()
end end
function Market.loadDepotItems(depotItems)
information.depotItems = {}
local items = {}
for i = 1, #depotItems do
local data = depotItems[i]
local id, count = data[1], data[2]
local tmpItem = Item.create(id)
if tmpItem:isStackable() then
if count > 100 then
local createCount = math.floor(count/100)
local remainder = count % 100
if remainder > 0 then
createCount = createCount + 1
end
for i = 1, createCount do
local newItem = Item.create(id)
if i == createCount and remainder > 0 then
newItem:setCount(remainder)
else
newItem:setCount(100)
end
table.insert(items, newItem)
end
else
local newItem = Item.create(id)
newItem:setCount(count)
table.insert(items, newItem)
end
else
for i = 1, count do
table.insert(items, Item.create(id))
end
end
end
for _, newItem in pairs(items) do
local marketData = newItem:getMarketData()
if not table.empty(marketData) then
local item = {
ptr = newItem,
marketData = marketData
}
table.insert(information.depotItems, item)
end
end
end
function Market.createNewOffer() function Market.createNewOffer()
local type = offerTypeList:getCurrentOption().text local type = offerTypeList:getCurrentOption().text
if type == 'Sell' then if type == 'Sell' then
@@ -1003,12 +1134,10 @@ function Market.createNewOffer()
if not Market.isItemSelected() then if not Market.isItemSelected() then
return return
end end
local item = selectedItem.item
local spriteId = item.ptr:getId() local spriteId = selectedItem.item.marketData.tradeAs
local piecePrice = piecePriceEdit:getValue() local piecePrice = piecePriceEdit:getValue()
local totalPrice = totalPriceEdit:getValue()
local amount = amountEdit:getValue() local amount = amountEdit:getValue()
local anonymous = anonymous:isChecked() and 1 or 0 local anonymous = anonymous:isChecked() and 1 or 0
@@ -1019,7 +1148,10 @@ function Market.createNewOffer()
errorMsg = errorMsg..'Not enough balance to create this offer.\n' errorMsg = errorMsg..'Not enough balance to create this offer.\n'
end end
elseif type == MarketAction.Sell then elseif type == MarketAction.Sell then
if Market.depotContains(spriteId) < amount then if information.balance < fee then
errorMsg = errorMsg..'Not enough balance to create this offer.\n'
end
if Market.getDepotCount(spriteId) < amount then
errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n' errorMsg = errorMsg..'Not enough items in your depot to create this offer.\n'
end end
end end
@@ -1029,12 +1161,21 @@ function Market.createNewOffer()
elseif piecePrice < piecePriceEdit.minimum then elseif piecePrice < piecePriceEdit.minimum then
errorMsg = errorMsg..'Price is too low.\n' errorMsg = errorMsg..'Price is too low.\n'
end end
if amount > amountEdit.maximum then if amount > amountEdit.maximum then
errorMsg = errorMsg..'Amount is too high.\n' errorMsg = errorMsg..'Amount is too high.\n'
elseif amount < amountEdit.minimum then elseif amount < amountEdit.minimum then
errorMsg = errorMsg..'Amount is too low.\n' errorMsg = errorMsg..'Amount is too low.\n'
end end
if amount * piecePrice > MarketMaxPrice then
errorMsg = errorMsg..'Total price is too high.\n'
end
if information.totalOffers >= MarketMaxOffers then
errorMsg = errorMsg..'You cannot create more offers.\n'
end
local timeCheck = os.time() - lastCreatedOffer local timeCheck = os.time() - lastCreatedOffer
if timeCheck < offerExhaust[type] then if timeCheck < offerExhaust[type] then
local waitTime = math.ceil(offerExhaust[type] - timeCheck) local waitTime = math.ceil(offerExhaust[type] - timeCheck)
@@ -1042,7 +1183,7 @@ function Market.createNewOffer()
end end
if errorMsg ~= '' then if errorMsg ~= '' then
displayInfoBox('Error', errorMsg) Market.displayMessage(errorMsg)
return return
end end
@@ -1060,9 +1201,6 @@ end
function Market.onItemBoxChecked(widget) function Market.onItemBoxChecked(widget)
if widget:isChecked() then if widget:isChecked() then
if selectedItem.ref and widget ~= selectedItem.ref then
selectedItem.ref:setChecked(false) -- temporary fix?
end
updateSelectedItem(widget) updateSelectedItem(widget)
end end
end end
@@ -1071,12 +1209,12 @@ end
function Market.onMarketEnter(depotItems, offers, balance, vocation) function Market.onMarketEnter(depotItems, offers, balance, vocation)
if not loaded then if not loaded then
initMarketItems(MarketCategory.All) initMarketItems()
loaded = true loaded = true
end end
Market.clearSelectedItem()
updateBalance(balance) updateBalance(balance)
averagePrice = 0
information.totalOffers = offers information.totalOffers = offers
local player = g_game.getLocalPlayer() local player = g_game.getLocalPlayer()
@@ -1092,10 +1230,12 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation)
information.vocation = vocation information.vocation = vocation
end end
Market.loadDepotItems(depotItems) -- set list of depot items
information.depotItems = depotItems
-- update the items widget to match depot items -- update the items widget to match depot items
if Market.isItemSelected() then if Market.isItemSelected() then
local spriteId = selectedItem.item.ptr:getId() local spriteId = selectedItem.item.marketData.tradeAs
MarketProtocol.silent(true) -- disable protocol messages MarketProtocol.silent(true) -- disable protocol messages
Market.refreshItemsWidget(spriteId) Market.refreshItemsWidget(spriteId)
MarketProtocol.silent(false) -- enable protocol messages MarketProtocol.silent(false) -- enable protocol messages
@@ -1107,14 +1247,6 @@ function Market.onMarketEnter(depotItems, offers, balance, vocation)
Market.loadMarketItems(MarketCategory.First) Market.loadMarketItems(MarketCategory.First)
end end
-- build offer table header
if buyOfferTable and not buyOfferTable:hasHeader() then
buyOfferTable:addHeaderRow(offerTableHeader)
end
if sellOfferTable and not sellOfferTable:hasHeader() then
sellOfferTable:addHeaderRow(offerTableHeader)
end
if g_game.isOnline() then if g_game.isOnline() then
marketWindow:lock() marketWindow:lock()
marketWindow:show() marketWindow:show()

View File

@@ -3,7 +3,7 @@ MarketWindow < MainWindow
!text: tr('Market') !text: tr('Market')
size: 700 530 size: 700 530
@onEscape: Market.close(true) @onEscape: Market.close()
// Main Panel Window // Main Panel Window
@@ -54,6 +54,7 @@ MarketWindow < MainWindow
Button Button
id: resetButton id: resetButton
!text: tr('Reset Market') !text: tr('Reset Market')
!tooltip: tr('Reset selection, filters & search')
anchors.top: mainTabContent.bottom anchors.top: mainTabContent.bottom
anchors.left: mainTabContent.left anchors.left: mainTabContent.left
margin-top: 5 margin-top: 5

View File

@@ -4,7 +4,7 @@ MarketOffer.__index = MarketOffer
local OFFER_TIMESTAMP = 1 local OFFER_TIMESTAMP = 1
local OFFER_COUNTER = 2 local OFFER_COUNTER = 2
MarketOffer.new = function(offerId, t, item, amount, price, playerName, state) MarketOffer.new = function(offerId, t, item, amount, price, playerName, state, var)
local offer = { local offer = {
id = {}, id = {},
type = nil, type = nil,
@@ -12,7 +12,8 @@ MarketOffer.new = function(offerId, t, item, amount, price, playerName, state)
amount = 0, amount = 0,
price = 0, price = 0,
player = '', player = '',
state = 0 state = 0,
var = nil
} }
if not offerId or type(offerId) ~= 'table' then if not offerId or type(offerId) ~= 'table' then
@@ -41,6 +42,7 @@ MarketOffer.new = function(offerId, t, item, amount, price, playerName, state)
g_logger.error('MarketOffer.new - invalid state provided.') g_logger.error('MarketOffer.new - invalid state provided.')
end end
offer.state = state offer.state = state
offer.var = var
setmetatable(offer, MarketOffer) setmetatable(offer, MarketOffer)
return offer return offer
@@ -153,4 +155,4 @@ function MarketOffer:getCounter()
return return
end end
return self.id[OFFER_COUNTER] return self.id[OFFER_COUNTER]
end end

View File

@@ -29,11 +29,12 @@ local function readMarketOffer(msg, action, var)
local state = MarketOfferState.Active local state = MarketOfferState.Active
if var == MarketRequest.MyHistory then if var == MarketRequest.MyHistory then
state = msg:getU8() state = msg:getU8()
elseif var == MarketRequest.MyOffers then
else else
playerName = msg:getString() playerName = msg:getString()
end end
return MarketOffer.new({timestamp, counter}, action, Item.create(itemId), amount, price, playerName, state) return MarketOffer.new({timestamp, counter}, action, Item.create(itemId), amount, price, playerName, state, var)
end end
-- parsing protocols -- parsing protocols
@@ -50,14 +51,14 @@ local function parseMarketEnter(protocol, msg)
vocation = msg:getU8() -- get vocation id vocation = msg:getU8() -- get vocation id
end end
local offers = msg:getU8() local offers = msg:getU8()
local depotItems = {}
local depotItems = {}
local depotCount = msg:getU16() local depotCount = msg:getU16()
for i = 1, depotCount do for i = 1, depotCount do
local itemId = msg:getU16() -- item id local itemId = msg:getU16() -- item id
local itemCount = msg:getU16() -- item count local itemCount = msg:getU16() -- item count
table.insert(depotItems, {itemId, itemCount}) depotItems[itemId] = itemCount
end end
signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation) signalcall(Market.onMarketEnter, depotItems, offers, balance, vocation)
@@ -201,6 +202,10 @@ function MarketProtocol.sendMarketBrowse(browseId)
end end
end end
function MarketProtocol.sendMarketBrowseMyOffers()
MarketProtocol.sendMarketBrowse(MarketRequest.MyOffers)
end
function MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, price, anonymous) function MarketProtocol.sendMarketCreateOffer(type, spriteId, amount, price, anonymous)
if g_game.getFeature(GamePlayerMarket) then if g_game.getFeature(GamePlayerMarket) then
local msg = OutputMessage.create() local msg = OutputMessage.create()

View File

@@ -1,7 +1,7 @@
AmountWindow < MainWindow AmountWindow < MainWindow
id: amountWindow id: amountWindow
!text: tr('Amount') !text: tr('Amount')
size: 270 80 size: 270 90
Item Item
id: item id: item

View File

@@ -4,14 +4,8 @@ MarketButtonBox < ButtonBoxRounded
size: 106 22 size: 106 22
text-offset: 0 2 text-offset: 0 2
text-align: center text-align: center
image-clip: 0 0 20 20
image-border: 2
$hover !disabled:
image-clip: 0 20 20 20
$checked: $checked:
image-clip: 0 40 20 20
color: white color: white
$disabled: $disabled:

View File

@@ -10,15 +10,9 @@ MarketTabBarButton < TabBarButton
margin-left: 0 margin-left: 0
$hover !checked: $hover !checked:
image-clip: 0 20 20 20 color: #ffffff
color: white
$disabled:
image-color: #ffffff66
icon-color: #888888
$checked: $checked:
image-clip: 0 20 20 20
color: #ffffff color: #ffffff
$on !checked: $on !checked:
@@ -26,36 +20,24 @@ MarketTabBarButton < TabBarButton
MarketRightTabBar < TabBar MarketRightTabBar < TabBar
MarketRightTabBarPanel < TabBarPanel MarketRightTabBarPanel < TabBarPanel
// TODO: inherit style from TabBarButton and adjust it MarketRightTabBarButton < TabBarButton
// anchors.left: none did not seem to work for me
MarketRightTabBarButton < UIButton
size: 20 25 size: 20 25
font: verdana-11px-rounded font: verdana-11px-rounded
text-offset: 0 2 text-offset: 0 2
image-source: /images/ui/tabbutton_square color: #929292
image-color: white
image-clip: 0 0 20 20
image-border: 3
icon-color: white
color: #aaaaaa
anchors.top: parent.top
padding: 5
anchors.right: prev.left
$first: $first:
anchors.right: parent.right anchors.right: parent.right
anchors.left: none
$!first:
anchors.right: prev.left
anchors.left: none
$hover !checked: $hover !checked:
image-clip: 0 20 20 20 color: #ffffff
color: white
$disabled:
image-color: #ffffff66
icon-color: #888888
$checked: $checked:
image-clip: 0 20 20 20
color: #ffffff color: #ffffff
$on !checked: $on !checked:

View File

@@ -135,8 +135,7 @@ Panel
font: verdana-11px-rounded font: verdana-11px-rounded
text-offset: 0 2 text-offset: 0 2
anchors.top: offerTypeLabel.top anchors.top: offerTypeLabel.top
anchors.left: prev.right anchors.left: amountEdit.left
margin-left: 32
PreviousButton PreviousButton
id: prevAmountButton id: prevAmountButton
@@ -147,7 +146,7 @@ Panel
SpinBox SpinBox
id: amountEdit id: amountEdit
anchors.verticalCenter: prev.verticalCenter anchors.top: prev.top
anchors.left: prev.right anchors.left: prev.right
margin-left: 3 margin-left: 3
width: 55 width: 55
@@ -184,8 +183,6 @@ Panel
Label Label
id: feeLabel id: feeLabel
font: verdana-11px-rounded font: verdana-11px-rounded
text-offset: 0 2
anchors.top: createOfferButton.bottom anchors.top: createOfferButton.bottom
anchors.right: parent.right anchors.left: createOfferButton.left
margin-right: 8 margin: 2
margin-top: 3

View File

@@ -52,7 +52,7 @@ Panel
MarketButtonBox MarketButtonBox
id: filterLevel id: filterLevel
checked: false &default: false
!text: tr('Level') !text: tr('Level')
!tooltip: tr('Filter list to match your level') !tooltip: tr('Filter list to match your level')
anchors.top: prev.bottom anchors.top: prev.bottom
@@ -62,11 +62,10 @@ Panel
margin-left: 3 margin-left: 3
width: 40 width: 40
height: 20 height: 20
@onCheckChange: Market.updateCurrentItems()
MarketButtonBox MarketButtonBox
id: filterVocation id: filterVocation
checked: false &default: false
!text: tr('Voc.') !text: tr('Voc.')
!tooltip: tr('Filter list to match your vocation') !tooltip: tr('Filter list to match your vocation')
anchors.top: prev.top anchors.top: prev.top
@@ -75,7 +74,6 @@ Panel
margin-left: 3 margin-left: 3
width: 34 width: 34
height: 20 height: 20
@onCheckChange: Market.updateCurrentItems()
MarketComboBox MarketComboBox
id: slotComboBox id: slotComboBox
@@ -90,7 +88,7 @@ Panel
MarketButtonBox MarketButtonBox
id: filterDepot id: filterDepot
checked: false &default: false
!text: tr('Show Depot Only') !text: tr('Show Depot Only')
!tooltip: tr('Show your depot items only') !tooltip: tr('Show your depot items only')
anchors.top: prev.bottom anchors.top: prev.bottom
@@ -99,7 +97,6 @@ Panel
margin-top: 6 margin-top: 6
margin-right: 3 margin-right: 3
margin-left: 3 margin-left: 3
@onCheckChange: Market.updateCurrentItems()
Panel Panel
id: itemsContainer id: itemsContainer
@@ -152,11 +149,10 @@ Panel
MarketButtonBox MarketButtonBox
id: filterSearchAll id: filterSearchAll
checked: false &default: true
!text: tr('All') !text: tr('All')
!tooltip: tr('Search all items') !tooltip: tr('Search all items')
anchors.verticalCenter: prev.verticalCenter anchors.verticalCenter: prev.verticalCenter
anchors.left: prev.right anchors.left: prev.right
anchors.right: itemsContainer.right anchors.right: itemsContainer.right
margin-left: 3 margin-left: 3
@onCheckChange: Market.updateCurrentItems()

View File

@@ -1,11 +1,12 @@
DetailsTableRow < TableRow DetailsTableRow < TableRow
font: verdana-11px-monochrome font: verdana-11px-monochrome
background-color: alpha
focusable: true focusable: true
color: #cccccc color: #cccccc
height: 45 height: 45
focusable: false focusable: false
padding: 2 padding: 2
even-background-color: alpha
odd-background-color: alpha
DetailsTableColumn < TableColumn DetailsTableColumn < TableColumn
font: verdana-11px-monochrome font: verdana-11px-monochrome

View File

@@ -1,35 +1,27 @@
OfferTableRow < TableRow OfferTableRow < TableRow
font: verdana-11px-monochrome font: verdana-11px-monochrome
background-color: alpha
focusable: true
color: #cccccc color: #cccccc
height: 15 height: 15
$focus:
background-color: #294f6d
color: #ffffff
OfferTableColumn < TableColumn OfferTableColumn < TableColumn
font: verdana-11px-monochrome font: verdana-11px-monochrome
background-color: alpha background-color: alpha
text-offset: 5 0 text-offset: 5 0
color: #cccccc color: #cccccc
width: 80 width: 80
focusable: false
OfferTableWarningColumn < OfferTableColumn
color: #e03d3d
OfferTableHeaderRow < TableHeaderRow OfferTableHeaderRow < TableHeaderRow
font: verdana-11px-monochrome font: verdana-11px-monochrome
focusable: false
color: #cccccc color: #cccccc
height: 20 height: 20
OfferTableHeaderColumn < TableHeaderColumn OfferTableHeaderColumn < SortableTableHeaderColumn
font: verdana-11px-monochrome font: verdana-11px-monochrome
background-color: alpha
text-offset: 2 0 text-offset: 2 0
color: #cccccc color: #cccccc
width: 80
focusable: true
$focus: $focus:
background-color: #294f6d background-color: #294f6d
@@ -74,8 +66,26 @@ Panel
table-data: sellingTableData table-data: sellingTableData
row-style: OfferTableRow row-style: OfferTableRow
column-style: OfferTableColumn column-style: OfferTableColumn
header-row-style: OfferTableHeaderRow header-column-style: false
header-column-style: OfferTableHeaderColumn header-row-style: false
OfferTableHeaderRow
id: header
OfferTableHeaderColumn
!text: tr('Buyer Name')
width: 100
OfferTableHeaderColumn
!text: tr('Amount')
width: 60
OfferTableHeaderColumn
!text: tr('Total Price')
width: 90
OfferTableHeaderColumn
!text: tr('Piece Price')
width: 80
OfferTableHeaderColumn
!text: tr('Auction End')
width: 120
TableData TableData
id: sellingTableData id: sellingTableData
@@ -129,8 +139,26 @@ Panel
table-data: buyingTableData table-data: buyingTableData
row-style: OfferTableRow row-style: OfferTableRow
column-style: OfferTableColumn column-style: OfferTableColumn
header-row-style: OfferTableHeaderRow header-column-style: false
header-column-style: OfferTableHeaderColumn header-row-style: false
OfferTableHeaderRow
id: header
OfferTableHeaderColumn
!text: tr('Seller Name')
width: 100
OfferTableHeaderColumn
!text: tr('Amount')
width: 60
OfferTableHeaderColumn
!text: tr('Total Price')
width: 90
OfferTableHeaderColumn
!text: tr('Piece Price')
width: 80
OfferTableHeaderColumn
!text: tr('Auction End')
width: 120
TableData TableData
id: buyingTableData id: buyingTableData

View File

@@ -1,6 +1,5 @@
StatsTableRow < TableRow StatsTableRow < TableRow
font: verdana-11px-monochrome font: verdana-11px-monochrome
background-color: alpha
focusable: true focusable: true
color: #cccccc color: #cccccc
height: 20 height: 20
@@ -9,7 +8,7 @@ StatsTableRow < TableRow
StatsTableColumn < TableColumn StatsTableColumn < TableColumn
font: verdana-11px-monochrome font: verdana-11px-monochrome
background-color: alpha background-color: alpha
text-offset: 5 0 text-offset: 5 3
color: #cccccc color: #cccccc
width: 110 width: 110
focusable: false focusable: false

View File

@@ -1,9 +1,176 @@
OfferTableRow < TableRow
font: verdana-11px-monochrome
color: #cccccc
height: 15
OfferTableColumn < TableColumn
font: verdana-11px-monochrome
background-color: alpha
text-offset: 5 0
color: #cccccc
width: 80
OfferTableWarningColumn < OfferTableColumn
color: #e03d3d
OfferTableHeaderRow < TableHeaderRow
font: verdana-11px-monochrome
color: #cccccc
height: 20
OfferTableHeaderColumn < SortableTableHeaderColumn
font: verdana-11px-monochrome
text-offset: 2 0
color: #cccccc
$focus:
background-color: #294f6d
color: #ffffff
Panel Panel
background-color: #22283399 background-color: #22283399
margin: 1 margin: 1
Button
id: sellCancelButton
!text: tr('Cancel')
anchors.right: parent.right
anchors.bottom: next.bottom
margin-right: 6
width: 80
enabled: false
Label Label
!text: tr('Current Offers') !text: tr('Sell Offers')
font: verdana-11px-rounded
text-offset: 0 2
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
margin-left: 10 margin-top: 44
margin-left: 6
Table
id: mySellingTable
anchors.top: prev.bottom
anchors.left: prev.left
anchors.right: parent.right
height: 150
margin-top: 5
margin-bottom: 5
margin-right: 6
padding: 1
focusable: false
background-color: #222833
border-width: 1
border-color: #191f27
table-data: mySellingTableData
row-style: OfferTableRow
column-style: OfferTableColumn
header-column-style: false
header-row-style: false
OfferTableHeaderRow
id: header
OfferTableHeaderColumn
!text: tr('Item Name')
width: 160
OfferTableHeaderColumn
!text: tr('Total Price')
width: 125
OfferTableHeaderColumn
!text: tr('Piece Price')
width: 125
OfferTableHeaderColumn
!text: tr('Amount')
width: 110
OfferTableHeaderColumn
!text: tr('Auction End')
width: 110
TableData
id: mySellingTableData
anchors.bottom: mySellingTable.bottom
anchors.left: mySellingTable.left
anchors.right: mySellingTable.right
margin-top: 2
vertical-scrollbar: mySellingTableScrollBar
VerticalScrollBar
id: mySellingTableScrollBar
anchors.top: mySellingTable.top
anchors.bottom: mySellingTable.bottom
anchors.right: mySellingTable.right
step: 28
pixels-scroll: true
Button
id: buyCancelButton
!text: tr('Cancel')
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 5
margin-right: 6
width: 80
enabled: false
Label
!text: tr('Buy Offers')
font: verdana-11px-rounded
text-offset: 0 2
anchors.top: prev.top
anchors.left: parent.left
margin-top: 9
margin-left: 6
Table
id: myBuyingTable
anchors.top: prev.bottom
anchors.left: prev.left
anchors.right: parent.right
margin-top: 5
margin-bottom: 5
margin-right: 6
height: 150
padding: 1
focusable: false
background-color: #222833
border-width: 1
border-color: #191f27
table-data: myBuyingTableData
row-style: OfferTableRow
column-style: OfferTableColumn
header-column-style: false
header-row-style: false
OfferTableHeaderRow
id: header
OfferTableHeaderColumn
!text: tr('Item Name')
width: 160
OfferTableHeaderColumn
!text: tr('Total Price')
width: 125
OfferTableHeaderColumn
!text: tr('Piece Price')
width: 125
OfferTableHeaderColumn
!text: tr('Amount')
width: 110
OfferTableHeaderColumn
!text: tr('Auction End')
width: 110
TableData
id: myBuyingTableData
anchors.bottom: myBuyingTable.bottom
anchors.left: myBuyingTable.left
anchors.right: myBuyingTable.right
vertical-scrollbar: myBuyingTableScrollBar
VerticalScrollBar
id: myBuyingTableScrollBar
anchors.top: myBuyingTable.top
anchors.bottom: myBuyingTable.bottom
anchors.right: myBuyingTable.right
step: 28
pixels-scroll: true

View File

@@ -35,16 +35,12 @@ function onModalDialog(id, title, message, buttons, enterButton, escapeButton, c
local messageLabel = modalDialog:getChildById('messageLabel') local messageLabel = modalDialog:getChildById('messageLabel')
local choiceList = modalDialog:getChildById('choiceList') local choiceList = modalDialog:getChildById('choiceList')
local choiceScrollbar = modalDialog:getChildById('choiceScrollBar') local choiceScrollbar = modalDialog:getChildById('choiceScrollBar')
local buttonList = modalDialog:getChildById('buttonList') local buttonsPanel = modalDialog:getChildById('buttonsPanel')
modalDialog:setText(title) modalDialog:setText(title)
messageLabel:setText(message) messageLabel:setText(message)
local horizontalPadding = modalDialog:getPaddingLeft() + modalDialog:getPaddingRight() local labelHeight
modalDialog:setWidth(math.min(modalDialog.maximumWidth, math.max(messageLabel:getWidth(), modalDialog.minimumWidth)))
messageLabel:setWidth(math.min(modalDialog.maximumWidth, math.max(messageLabel:getWidth(), modalDialog.minimumWidth)) - horizontalPadding)
local labelHeight = nil
for i = 1, #choices do for i = 1, #choices do
local choiceId = choices[i][1] local choiceId = choices[i][1]
local choiceName = choices[i][2] local choiceName = choices[i][2]
@@ -59,11 +55,12 @@ function onModalDialog(id, title, message, buttons, enterButton, escapeButton, c
end end
choiceList:focusNextChild() choiceList:focusNextChild()
local buttonsWidth = 0
for i = 1, #buttons do for i = 1, #buttons do
local buttonId = buttons[i][1] local buttonId = buttons[i][1]
local buttonText = buttons[i][2] local buttonText = buttons[i][2]
local button = g_ui.createWidget('ModalButton', buttonList) local button = g_ui.createWidget('ModalButton', buttonsPanel)
button:setText(buttonText) button:setText(buttonText)
button.onClick = function(self) button.onClick = function(self)
local focusedChoice = choiceList:getFocusedChild() local focusedChoice = choiceList:getFocusedChild()
@@ -74,6 +71,7 @@ function onModalDialog(id, title, message, buttons, enterButton, escapeButton, c
g_game.answerModalDialog(id, buttonId, choice) g_game.answerModalDialog(id, buttonId, choice)
destroyDialog() destroyDialog()
end end
buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft() + button:getMarginRight()
end end
local additionalHeight = 0 local additionalHeight = 0
@@ -84,11 +82,13 @@ function onModalDialog(id, title, message, buttons, enterButton, escapeButton, c
additionalHeight = math.min(modalDialog.maximumChoices, math.max(modalDialog.minimumChoices, #choices)) * labelHeight additionalHeight = math.min(modalDialog.maximumChoices, math.max(modalDialog.minimumChoices, #choices)) * labelHeight
additionalHeight = additionalHeight + choiceList:getPaddingTop() + choiceList:getPaddingBottom() additionalHeight = additionalHeight + choiceList:getPaddingTop() + choiceList:getPaddingBottom()
end end
modalDialog:setHeight(modalDialog:getHeight() + additionalHeight)
addEvent(function() local horizontalPadding = modalDialog:getPaddingLeft() + modalDialog:getPaddingRight()
modalDialog:setHeight(modalDialog:getHeight() + messageLabel:getHeight() - 14) buttonsWidth = buttonsWidth + horizontalPadding
end)
modalDialog:setWidth(math.min(modalDialog.maximumWidth, math.max(buttonsWidth, messageLabel:getWidth(), modalDialog.minimumWidth)))
messageLabel:setWidth(math.min(modalDialog.maximumWidth, math.max(buttonsWidth, messageLabel:getWidth(), modalDialog.minimumWidth)) - horizontalPadding)
modalDialog:setHeight(modalDialog:getHeight() + additionalHeight + messageLabel:getHeight() - 8)
local enterFunc = function() local enterFunc = function()
local focusedChoice = choiceList:getFocusedChild() local focusedChoice = choiceList:getFocusedChild()

View File

@@ -5,7 +5,7 @@ ChoiceListLabel < Label
focusable: true focusable: true
$focus: $focus:
background-color: #ffffff22 background-color: #00000055
color: #ffffff color: #ffffff
ChoiceList < TextList ChoiceList < TextList
@@ -14,7 +14,6 @@ ChoiceList < TextList
anchors.fill: parent anchors.fill: parent
anchors.top: prev.bottom anchors.top: prev.bottom
anchors.bottom: next.top anchors.bottom: next.top
padding: 1
margin-top: 4 margin-top: 4
margin-bottom: 10 margin-bottom: 10
focusable: false focusable: false
@@ -30,14 +29,19 @@ ChoiceScrollBar < VerticalScrollBar
visible: false visible: false
ModalButton < Button ModalButton < Button
width: 60 text-auto-resize: true
margin: 2 margin-top: 2
margin-bottom: 2
margin-left: 2
$pressed:
text-offset: 0 0
ModalDialog < MainWindow ModalDialog < MainWindow
id: modalDialog id: modalDialog
size: 280 97 size: 280 97
&minimumWidth: 200 &minimumWidth: 200
&maximumWidth: 500 &maximumWidth: 600
&minimumChoices: 4 &minimumChoices: 4
&maximumChoices: 10 &maximumChoices: 10
@@ -57,7 +61,7 @@ ModalDialog < MainWindow
anchors.bottom: next.top anchors.bottom: next.top
Panel Panel
id: buttonList id: buttonsPanel
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@@ -177,6 +177,7 @@ function itemPopup(self, mousePosition, mouseButton)
if mouseButton == MouseRightButton then if mouseButton == MouseRightButton then
local menu = g_ui.createWidget('PopupMenu') local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
menu:addOption(tr('Look'), function() return g_game.inspectNpcTrade(self:getItem()) end) menu:addOption(tr('Look'), function() return g_game.inspectNpcTrade(self:getItem()) end)
menu:display(mousePosition) menu:display(mousePosition)
return true return true

View File

@@ -25,13 +25,17 @@ mountCreature = nil
currentMount = 1 currentMount = 1
function init() function init()
connect(g_game, { onOpenOutfitWindow = create, connect(g_game, {
onGameEnd = destroy }) onOpenOutfitWindow = create,
onGameEnd = destroy
})
end end
function terminate() function terminate()
disconnect(g_game, { onOpenOutfitWindow = create, disconnect(g_game, {
onGameEnd = destroy }) onOpenOutfitWindow = create,
onGameEnd = destroy
})
destroy() destroy()
end end
@@ -300,17 +304,11 @@ function updateOutfit()
addon.widget:setChecked(false) addon.widget:setChecked(false)
addon.widget:setEnabled(false) addon.widget:setEnabled(false)
end end
outfit.addons = 0
if availableAddons > 0 then if availableAddons > 0 then
for _, i in pairs(ADDON_SETS[availableAddons]) do for _, i in pairs(ADDON_SETS[availableAddons]) do
addons[i].widget:setEnabled(true) addons[i].widget:setEnabled(true)
end
end
outfit.addons = 0
for i = 1, #prevAddons do
local addon = prevAddons[i]
if addon and addons[i].widget:isEnabled() then
addons[i].widget:setChecked(true) addons[i].widget:setChecked(true)
end end
end end

View File

@@ -1,10 +1,11 @@
DeathWindow < MainWindow DeathWindow < MainWindow
id: deathWindow id: deathWindow
!text: tr('You are dead') !text: tr('You are dead')
size: 350 155 &baseWidth: 350
&baseHeight: 15
Label Label
!text: tr('Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!') id: labelText
width: 550 width: 550
height: 140 height: 140
anchors.left: parent.left anchors.left: parent.left

View File

@@ -1,5 +1,11 @@
deathWindow = nil deathWindow = nil
local deathTexts = {
regular = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nSimply click on Ok to resume your journeys!', height = 140, width = 0},
unfair = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back\ninto this world in exchange for a small sacrifice\n\nThis death penalty has been reduced by %i%%\nbecause it was an unfair fight.\n\nSimply click on Ok to resume your journeys!', height = 185, width = 0},
blessed = {text = 'Alas! Brave adventurer, you have met a sad fate.\nBut do not despair, for the gods will bring you back into this world\n\nThis death penalty has been reduced by 100%\nbecause you are blessed with the Adventurer\'s Blessing\n\nSimply click on Ok to resume your journeys!', height = 170, width = 90}
}
function init() function init()
g_ui.importStyle('deathwindow') g_ui.importStyle('deathwindow')
@@ -21,9 +27,9 @@ function reset()
end end
end end
function display() function display(deathType, penalty)
displayDeadMessage() displayDeadMessage()
openWindow() openWindow(deathType, penalty)
end end
function displayDeadMessage() function displayDeadMessage()
@@ -33,12 +39,31 @@ function displayDeadMessage()
modules.game_textmessage.displayGameMessage(tr('You are dead.')) modules.game_textmessage.displayGameMessage(tr('You are dead.'))
end end
function openWindow() function openWindow(deathType, penalty)
if deathWindow then if deathWindow then
deathWindow:destroy() deathWindow:destroy()
return return
end end
deathWindow = g_ui.createWidget('DeathWindow', rootWidget) deathWindow = g_ui.createWidget('DeathWindow', rootWidget)
local textLabel = deathWindow:getChildById('labelText')
if deathType == DeathType.Regular then
if penalty == 100 then
textLabel:setText(deathTexts.regular.text)
deathWindow:setHeight(deathWindow.baseHeight + deathTexts.regular.height)
deathWindow:setWidth(deathWindow.baseWidth + deathTexts.regular.width)
else
textLabel:setText(string.format(deathTexts.unfair.text, 100 - penalty))
deathWindow:setHeight(deathWindow.baseHeight + deathTexts.unfair.height)
deathWindow:setWidth(deathWindow.baseWidth + deathTexts.unfair.width)
end
elseif deathType == DeathType.Blessed then
textLabel:setText(deathTexts.blessed.text)
deathWindow:setHeight(deathWindow.baseHeight + deathTexts.blessed.height)
deathWindow:setWidth(deathWindow.baseWidth + deathTexts.blessed.width)
end
local okButton = deathWindow:getChildById('buttonOk') local okButton = deathWindow:getChildById('buttonOk')
local cancelButton = deathWindow:getChildById('buttonCancel') local cancelButton = deathWindow:getChildById('buttonCancel')

View File

@@ -44,7 +44,6 @@ MessageTypes = {
[MessageModes.Party] = MessageSettings.centerGreen, [MessageModes.Party] = MessageSettings.centerGreen,
[MessageModes.PartyManagement] = MessageSettings.centerWhite, [MessageModes.PartyManagement] = MessageSettings.centerWhite,
[MessageModes.TutorialHint] = MessageSettings.centerWhite, [MessageModes.TutorialHint] = MessageSettings.centerWhite,
[MessageModes.Market] = MessageSettings.centerWhite,
[MessageModes.BeyondLast] = MessageSettings.centerWhite, [MessageModes.BeyondLast] = MessageSettings.centerWhite,
[MessageModes.Report] = MessageSettings.consoleRed, [MessageModes.Report] = MessageSettings.consoleRed,
[MessageModes.HotkeyUse] = MessageSettings.centerGreen, [MessageModes.HotkeyUse] = MessageSettings.centerGreen,
@@ -55,13 +54,19 @@ MessageTypes = {
messagesPanel = nil messagesPanel = nil
function init() function init()
connect(g_game, 'onTextMessage', displayMessage) for messageMode, _ in pairs(MessageTypes) do
registerMessageMode(messageMode, displayMessage)
end
connect(g_game, 'onGameEnd', clearMessages) connect(g_game, 'onGameEnd', clearMessages)
messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel()) messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel())
end end
function terminate() function terminate()
disconnect(g_game, 'onTextMessage', displayMessage) for messageMode, _ in pairs(MessageTypes) do
unregisterMessageMode(messageMode, displayMessage)
end
disconnect(g_game, 'onGameEnd', clearMessages) disconnect(g_game, 'onGameEnd', clearMessages)
clearMessages() clearMessages()
messagesPanel:destroy() messagesPanel:destroy()
@@ -75,9 +80,7 @@ function displayMessage(mode, text)
if not g_game.isOnline() then return end if not g_game.isOnline() then return end
local msgtype = MessageTypes[mode] local msgtype = MessageTypes[mode]
if not msgtype then if not msgtype then
perror('unhandled onTextMessage message mode ' .. mode .. ': ' .. text)
return return
end end

View File

@@ -8,6 +8,7 @@ TextMessageLabel < UILabel
Panel Panel
anchors.fill: gameMapPanel anchors.fill: gameMapPanel
anchors.bottom: gameBottomPanel.top
focusable: false focusable: false
Panel Panel

View File

@@ -0,0 +1,148 @@
unjustifiedPointsWindow = nil
unjustifiedPointsButton = nil
contentsPanel = nil
openPvpSituationsLabel = nil
currentSkullWidget = nil
skullTimeLabel = nil
dayProgressBar = nil
weekProgressBar = nil
monthProgressBar = nil
daySkullWidget = nil
weekSkullWidget = nil
monthSkullWidget = nil
function init()
connect(g_game, { onGameStart = online,
onUnjustifiedPointsChange = onUnjustifiedPointsChange,
onOpenPvpSituationsChange = onOpenPvpSituationsChange })
connect(LocalPlayer, { onSkullChange = onSkullChange } )
unjustifiedPointsButton = modules.client_topmenu.addRightGameToggleButton('unjustifiedPointsButton',
tr('Unjustified Points'), '/images/topbuttons/unjustifiedpoints', toggle)
unjustifiedPointsButton:setOn(true)
unjustifiedPointsButton:hide()
unjustifiedPointsWindow = g_ui.loadUI('unjustifiedpoints', modules.game_interface.getRightPanel())
unjustifiedPointsWindow:disableResize()
unjustifiedPointsWindow:setup()
contentsPanel = unjustifiedPointsWindow:getChildById('contentsPanel')
openPvpSituationsLabel = contentsPanel:getChildById('openPvpSituationsLabel')
currentSkullWidget = contentsPanel:getChildById('currentSkullWidget')
skullTimeLabel = contentsPanel:getChildById('skullTimeLabel')
dayProgressBar = contentsPanel:getChildById('dayProgressBar')
weekProgressBar = contentsPanel:getChildById('weekProgressBar')
monthProgressBar = contentsPanel:getChildById('monthProgressBar')
daySkullWidget = contentsPanel:getChildById('daySkullWidget')
weekSkullWidget = contentsPanel:getChildById('weekSkullWidget')
monthSkullWidget = contentsPanel:getChildById('monthSkullWidget')
if g_game.isOnline() then
online()
end
end
function terminate()
disconnect(g_game, { onGameStart = online,
onUnjustifiedPointsChange = onUnjustifiedPointsChange,
onOpenPvpSituationsChange = onOpenPvpSituationsChange })
disconnect(LocalPlayer, { onSkullChange = onSkullChange } )
unjustifiedPointsWindow:destroy()
unjustifiedPointsButton:destroy()
end
function onMiniWindowClose()
unjustifiedPointsButton:setOn(false)
end
function toggle()
if unjustifiedPointsButton:isOn() then
unjustifiedPointsWindow:close()
unjustifiedPointsButton:setOn(false)
else
unjustifiedPointsWindow:open()
unjustifiedPointsButton:setOn(true)
end
end
function online()
if g_game.getFeature(GameUnjustifiedPoints) then
unjustifiedPointsButton:show()
else
unjustifiedPointsButton:hide()
unjustifiedPointsWindow:close()
end
refresh()
end
function refresh()
local localPlayer = g_game.getLocalPlayer()
local unjustifiedPoints = g_game.getUnjustifiedPoints()
onUnjustifiedPointsChange(unjustifiedPoints)
onSkullChange(localPlayer, localPlayer:getSkull())
onOpenPvpSituationsChange(g_game.getOpenPvpSituations())
end
function onSkullChange(localPlayer, skull)
if not localPlayer:isLocalPlayer() then return end
if skull == SkullRed or skull == SkullBlack then
currentSkullWidget:setIcon(getSkullImagePath(skull))
currentSkullWidget:setTooltip('Remaining skull time')
else
currentSkullWidget:setIcon('')
currentSkullWidget:setTooltip('You have no skull')
end
daySkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull)))
weekSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull)))
monthSkullWidget:setIcon(getSkullImagePath(getNextSkullId(skull)))
end
function onOpenPvpSituationsChange(amount)
openPvpSituationsLabel:setText(amount)
end
local function getColorByKills(kills)
if kills < 2 then
return 'red'
elseif kills < 3 then
return 'yellow'
end
return 'green'
end
function onUnjustifiedPointsChange(unjustifiedPoints)
if unjustifiedPoints.skullTime == 0 then
skullTimeLabel:setText('No skull')
skullTimeLabel:setTooltip('You have no skull')
else
skullTimeLabel:setText(unjustifiedPoints.skullTime .. ' days')
skullTimeLabel:setTooltip('Remaining skull time')
end
dayProgressBar:setValue(unjustifiedPoints.killsDay, 0, 100)
dayProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsDayRemaining))
dayProgressBar:setTooltip(string.format('Unjustified points gained during the last 24 hours.\n%i kill%s left.', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's')))
dayProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsDayRemaining, (unjustifiedPoints.killsDayRemaining == 1 and '' or 's')))
weekProgressBar:setValue(unjustifiedPoints.killsWeek, 0, 100)
weekProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsWeekRemaining))
weekProgressBar:setTooltip(string.format('Unjustified points gained during the last 7 days.\n%i kill%s left.', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's')))
weekProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsWeekRemaining, (unjustifiedPoints.killsWeekRemaining == 1 and '' or 's')))
monthProgressBar:setValue(unjustifiedPoints.killsMonth, 0, 100)
monthProgressBar:setBackgroundColor(getColorByKills(unjustifiedPoints.killsMonthRemaining))
monthProgressBar:setTooltip(string.format('Unjustified points gained during the last 30 days.\n%i kill%s left.', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's')))
monthProgressBar:setText(string.format('%i kill%s left', unjustifiedPoints.killsMonthRemaining, (unjustifiedPoints.killsMonthRemaining == 1 and '' or 's')))
end

View File

@@ -0,0 +1,8 @@
Module
name: game_unjustifiedpoints
description: View unjustified points
author: Summ
sandboxed: true
scripts: [ unjustifiedpoints ]
@onLoad: init()
@onUnload: terminate()

View File

@@ -0,0 +1,80 @@
SkullProgressBar < ProgressBar
height: 13
margin: 4 18 0 10
anchors.top: prev.bottom
anchors.left: parent.left
anchors.right: parent.right
SkullWidget < UIWidget
size: 13 13
margin-right: 2
anchors.right: parent.right
image-source: /images/game/skull_socket
MiniWindow
id: unjustifiedPointsWindow
!text: tr('Unjustified Points')
height: 114
icon: /images/topbuttons/unjustifiedpoints
@onClose: modules.game_unjustifiedpoints.onMiniWindowClose()
&save: true
MiniWindowContents
Label
anchors.top: parent.top
anchors.left: parent.left
!text: tr('Open PvP')
!tooltip: tr('Open PvP Situations')
phantom: false
margin-top: 2
margin-left: 10
Label
id: openPvpSituationsLabel
anchors.top: prev.bottom
anchors.left: parent.left
font: verdana-11px-rounded
margin-left: 12
phantom: false
Label
anchors.top: parent.top
anchors.right: parent.right
!text: tr('Skull Time')
margin-top: 2
margin-right: 10
SkullWidget
id: currentSkullWidget
anchors.top: prev.bottom
margin-right: 10
Label
id: skullTimeLabel
anchors.top: prev.top
anchors.right: prev.left
font: verdana-11px-rounded
margin-right: 6
phantom: false
SkullProgressBar
id: dayProgressBar
margin-top: 10
SkullWidget
id: daySkullWidget
anchors.top: prev.top
SkullProgressBar
id: weekProgressBar
SkullWidget
id: weekSkullWidget
anchors.top: prev.top
SkullProgressBar
id: monthProgressBar
SkullWidget
id: monthSkullWidget
anchors.top: prev.top

View File

@@ -92,7 +92,9 @@ function onMiniWindowClose()
end end
function createAddWindow() function createAddWindow()
addVipWindow = g_ui.displayUI('addvip') if not addVipWindow then
addVipWindow = g_ui.displayUI('addvip')
end
end end
function createEditWindow(widget) function createEditWindow(widget)
@@ -343,6 +345,7 @@ function onVipListMousePress(widget, mousePos, mouseButton)
local vipList = vipWindow:getChildById('contentsPanel') local vipList = vipWindow:getChildById('contentsPanel')
local menu = g_ui.createWidget('PopupMenu') local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
menu:addOption(tr('Add new VIP'), function() createAddWindow() end) menu:addOption(tr('Add new VIP'), function() createAddWindow() end)
menu:addSeparator() menu:addSeparator()
@@ -375,6 +378,7 @@ function onVipListLabelMousePress(widget, mousePos, mouseButton)
local vipList = vipWindow:getChildById('contentsPanel') local vipList = vipWindow:getChildById('contentsPanel')
local menu = g_ui.createWidget('PopupMenu') local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
menu:addOption(tr('Send Message'), function() g_game.openPrivateChannel(widget:getText()) end) menu:addOption(tr('Send Message'), function() g_game.openPrivateChannel(widget:getText()) end)
menu:addOption(tr('Add new VIP'), function() createAddWindow() end) menu:addOption(tr('Add new VIP'), function() createAddWindow() end)
menu:addOption(tr('Edit %s', widget:getText()), function() if widget then createEditWindow(widget) end end) menu:addOption(tr('Edit %s', widget:getText()), function() if widget then createEditWindow(widget) end end)

View File

@@ -120,6 +120,17 @@ GameSpritesAlphaChannel = 56
GamePremiumExpiration = 57 GamePremiumExpiration = 57
GameBrowseField = 58 GameBrowseField = 58
GameEnhancedAnimations = 59 GameEnhancedAnimations = 59
GameOGLInformation = 60
GameMessageSizeCheck = 61
GamePreviewState = 62
GameLoginPacketEncryption = 63
GameClientVersion = 64
GameContentRevision = 65
GameExperienceBonus = 66
GameAuthenticator = 67
GameUnjustifiedPoints = 68
GameSessionKey = 69
GameDeathType = 70
TextColors = { TextColors = {
red = '#f55e5e', --'#c83200' red = '#f55e5e', --'#c83200'
@@ -184,7 +195,9 @@ MessageModes = {
RVRChannel = 46, RVRChannel = 46,
RVRAnswer = 47, RVRAnswer = 47,
RVRContinue = 48, RVRContinue = 48,
Last = 49, GameHighlight = 49,
NpcFromStartBlock = 50,
Last = 51,
Invalid = 255, Invalid = 255,
} }
@@ -201,7 +214,7 @@ CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618"
"88792221429527047321331896351555606801473202394175817" "88792221429527047321331896351555606801473202394175817"
-- set to the latest Tibia.pic signature to make otclient compatible with official tibia -- set to the latest Tibia.pic signature to make otclient compatible with official tibia
PIC_SIGNATURE = 0x53208400 PIC_SIGNATURE = 0x542100C1
OsTypes = { OsTypes = {
Linux = 1, Linux = 1,
@@ -244,4 +257,25 @@ ExtendedIds = {
NeedsUpdate = 7 NeedsUpdate = 7
} }
PreviewState = {
Default = 0,
Inactive = 1,
Active = 2
}
Blessings = {
None = 0,
Adventurer = 1,
SpiritualShielding = 2,
EmbraceOfTibia = 4,
FireOfSuns = 8,
WisdomOfSolitude = 16,
SparkOfPhoenix = 32
}
DeathType = {
Regular = 0,
Blessed = 1
}
-- @} -- @}

View File

@@ -35,6 +35,13 @@ NpcIconTradeQuest = 4
-- @} -- @}
function getNextSkullId(skullId)
if skullId == SkullRed or skullId == SkullBlack then
return SkullBlack
end
return SkullRed
end
function getSkullImagePath(skullId) function getSkullImagePath(skullId)
local path local path
if skullId == SkullYellow then if skullId == SkullYellow then

View File

@@ -1,7 +1,5 @@
local currentRsa
function g_game.getRsa() function g_game.getRsa()
return currentRsa return G.currentRsa
end end
function g_game.findPlayerItem(itemId, subType) function g_game.findPlayerItem(itemId, subType)
@@ -19,7 +17,7 @@ function g_game.findPlayerItem(itemId, subType)
end end
function g_game.chooseRsa(host) function g_game.chooseRsa(host)
if currentRsa ~= CIPSOFT_RSA and currentRsa ~= OTSERV_RSA then return end if G.currentRsa ~= CIPSOFT_RSA and G.currentRsa ~= OTSERV_RSA then return end
if host:ends('.tibia.com') or host:ends('.cipsoft.com') then if host:ends('.tibia.com') or host:ends('.cipsoft.com') then
g_game.setRsa(CIPSOFT_RSA) g_game.setRsa(CIPSOFT_RSA)
@@ -29,57 +27,64 @@ function g_game.chooseRsa(host)
g_game.setCustomOs(OsTypes.Linux) g_game.setCustomOs(OsTypes.Linux)
end end
else else
if currentRsa == CIPSOFT_RSA then if G.currentRsa == CIPSOFT_RSA then
g_game.setCustomOs(-1) g_game.setCustomOs(-1)
end end
g_game.setRsa(OTSERV_RSA) g_game.setRsa(OTSERV_RSA)
end end
-- Hack fix to resolve some 760 login issues
if g_game.getClientVersion() <= 760 then
g_game.setCustomOs(2)
end
end end
function g_game.setRsa(rsa, e) function g_game.setRsa(rsa, e)
e = e or '65537' e = e or '65537'
g_crypt.rsaSetPublicKey(rsa, e) g_crypt.rsaSetPublicKey(rsa, e)
currentRsa = rsa G.currentRsa = rsa
end end
function g_game.isOfficialTibia() function g_game.isOfficialTibia()
return currentRsa == CIPSOFT_RSA return G.currentRsa == CIPSOFT_RSA
end end
function g_game.getSupportedClients() function g_game.getSupportedClients()
return { return {
740, 741, 750, 760, 770, 772, 740, 741, 750, 760, 770, 772,
780, 781, 782, 790, 792, 780, 781, 782, 790, 792,
800, 810, 811, 820, 821, 822, 800, 810, 811, 820, 821, 822,
830, 831, 840, 842, 850, 853, 830, 831, 840, 842, 850, 853,
854, 855, 857, 860, 861, 862, 854, 855, 857, 860, 861, 862,
870, 871, 870, 871,
900, 910, 920, 931, 940, 943, 900, 910, 920, 931, 940, 943,
944, 951, 952, 953, 954, 960, 944, 951, 952, 953, 954, 960,
961, 963, 970, 971, 972, 973, 961, 963, 970, 971, 972, 973,
980, 981, 982, 983, 984, 985, 980, 981, 982, 983, 984, 985,
986, 986,
1000, 1001, 1002, 1010, 1011, 1000, 1001, 1002, 1010, 1011,
1012, 1013, 1020, 1021, 1022, 1012, 1013, 1020, 1021, 1022,
1030, 1031, 1032, 1033, 1034, 1030, 1031, 1032, 1033, 1034,
1035, 1036, 1037, 1038, 1039, 1035, 1036, 1037, 1038, 1039,
1040, 1041, 1050, 1051, 1052, 1040, 1041, 1050, 1051, 1052,
1053, 1054, 1055, 1056, 1057, 1053, 1054, 1055, 1056, 1057,
1058, 1059, 1060, 1061 1058, 1059, 1060, 1061, 1062,
1063, 1064, 1070, 1071, 1072,
1073, 1074, 1075, 1076
} }
end end
-- The client version and protocol version where -- The client version and protocol version where
-- unsynchronized for some releases, not sure if this -- unsynchronized for some releases, not sure if this
-- will be the normal standard. -- will be the normal standard.
-- Client Version: Publicly given version when -- Client Version: Publicly given version when
-- downloading Cipsoft client. -- downloading Cipsoft client.
-- Protocol Version: Previously was the same as -- Protocol Version: Previously was the same as
-- the client version, but was unsychronized in some -- the client version, but was unsychronized in some
-- releases, now it needs to be verified and added here -- releases, now it needs to be verified and added here
-- if it does not match the client version. -- if it does not match the client version.
@@ -87,7 +92,7 @@ end
-- Reason for defining both: The server now requires a -- Reason for defining both: The server now requires a
-- Client version and Protocol version from the client. -- Client version and Protocol version from the client.
-- Important: Use getClientVersion for specific protocol -- Important: Use getClientVersion for specific protocol
-- features to ensure we are using the proper version. -- features to ensure we are using the proper version.
function g_game.getClientProtocolVersion(client) function g_game.getClientProtocolVersion(client)
@@ -105,4 +110,6 @@ function g_game.getClientProtocolVersion(client)
return clients[client] or client return clients[client] or client
end end
g_game.setRsa(OTSERV_RSA) if not G.currentRsa then
g_game.setRsa(OTSERV_RSA)
end

View File

@@ -19,6 +19,7 @@ Module
dofile 'creature' dofile 'creature'
dofile 'player' dofile 'player'
dofile 'market' dofile 'market'
dofile 'textmessages'
dofile 'thing' dofile 'thing'
dofile 'spells' dofile 'spells'

Some files were not shown because too many files have changed in this diff Show More