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
Makefile
/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
/*.cxx
*.o
*.gch
*.a
*.exe
*.so
*.spr
*.dat
*.kdev*

View File

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

View File

@@ -1,6 +1,6 @@
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
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?
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.
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?
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:
* [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 OS X](https://github.com/edubart/otclient/wiki/Compiling-on-Mac-OS-X)
### 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",
languageName = "Deutsch",
formatNumbers = true,
decimalSeperator = ',',
thousandsSeperator = ' ',
translation = {
["1a) Offensive Name"] = false,
["1b) Invalid Name Format"] = false,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,26 +1,62 @@
Table < UITable
layout: verticalBox
header-column-style: HeaderTableColumn
header-row-style: HeaderTableRow
header-column-style: TableHeaderColumn
header-row-style: TableHeaderRow
column-style: TableColumn
row-style: TableRow
TableData < UIScrollArea
layout: verticalBox
TableRow < Label
TableRow < UITableRow
layout: horizontalBox
height: 10
text-wrap: true
focusable: true
even-background-color: alpha
odd-background-color: #00000022
$focus:
background-color: #294f6d
color: #ffffff
TableColumn < Label
width: 30
text-wrap: true
focusable: false
TableHeaderRow < Label
layout: horizontalBox
focusable: false
height: 10
text-wrap: true
TableHeaderColumn < Button
width: 30
TableHeaderColumn < UITableHeaderColumn
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()
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...'))
connect(loadBox, { onCancel = function()
@@ -109,6 +109,16 @@ function onGameLoginError(message)
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)
CharacterList.destroyLoadBox()
local text = translateNetworkError(code, g_game.getProtocolGame() and g_game.getProtocolGame():isConnecting(), message)
@@ -131,6 +141,7 @@ end
-- public functions
function CharacterList.init()
connect(g_game, { onLoginError = onGameLoginError })
connect(g_game, { onLoginToken = onGameLoginToken })
connect(g_game, { onUpdateNeeded = onGameUpdateNeeded })
connect(g_game, { onConnectionError = onGameConnectionError })
connect(g_game, { onGameStart = CharacterList.destroyLoadBox })
@@ -144,6 +155,7 @@ end
function CharacterList.terminate()
disconnect(g_game, { onLoginError = onGameLoginError })
disconnect(g_game, { onLoginToken = onGameLoginToken })
disconnect(g_game, { onUpdateNeeded = onGameUpdateNeeded })
disconnect(g_game, { onConnectionError = onGameConnectionError })
disconnect(g_game, { onGameStart = CharacterList.destroyLoadBox })

View File

@@ -16,7 +16,7 @@ CharacterWidget < UIWidget
Label
id: name
color: #aaaaaa
color: #bbbbbb
anchors.top: parent.top
anchors.left: parent.left
font: verdana-11px-monochrome
@@ -29,8 +29,7 @@ CharacterWidget < UIWidget
Label
id: worldName
color: #ffffff
color: #aaaaaa
color: #bbbbbb
anchors.top: parent.top
anchors.right: parent.right
margin-right: 5
@@ -45,16 +44,21 @@ CharacterWidget < UIWidget
MainWindow
id: charactersWindow
!text: tr('Character List')
size: 250 248
visible: false
@onEnter: CharacterList.doLogin()
@onEscape: CharacterList.hide(true)
@onSetup: |
g_keyboard.bindKeyPress('Up', function() self:getChildById('characters'):focusPreviousChild(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
id: characters
background-color: #565656
anchors.top: parent.top
anchors.left: parent.left
anchors.right: characterListScrollBar.left

View File

@@ -33,10 +33,17 @@ local function onMotd(protocol, motd)
end
end
local function onSessionKey(protocol, sessionKey)
G.sessionKey = sessionKey
end
local function onCharacterList(protocol, characters, account, otui)
-- Try add server to the server list
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
local account = g_crypt.encrypt(G.account)
local password = g_crypt.encrypt(G.password)
@@ -59,13 +66,19 @@ local function onCharacterList(protocol, characters, account, otui)
loadBox:destroy()
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.show()
if motdEnabled then
local lastMotdNumber = g_settings.getNumber("motd")
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)
connect(motdWindow, { onOk = function() CharacterList.show() motdWindow = nil end })
CharacterList.hide()
@@ -103,9 +116,10 @@ function EnterGame.init()
local password = g_settings.get('password')
local host = g_settings.get('host')
local port = g_settings.get('port')
local stayLogged = g_settings.getBoolean('staylogged')
local autologin = g_settings.getBoolean('autologin')
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
@@ -115,6 +129,7 @@ function EnterGame.init()
enterGame:getChildById('serverHostTextEdit'):setText(host)
enterGame:getChildById('serverPortTextEdit'):setText(port)
enterGame:getChildById('autoLoginBox'):setChecked(autologin)
enterGame:getChildById('stayLoggedBox'):setChecked(stayLogged)
clientBox = enterGame:getChildById('clientComboBox')
for _, proto in pairs(g_game.getSupportedClients()) do
@@ -122,6 +137,10 @@ function EnterGame.init()
end
clientBox:setCurrentOption(clientVersion)
EnterGame.toggleAuthenticatorToken(clientVersion, true)
EnterGame.toggleStayLoggedBox(clientVersion, true)
connect(clientBox, { onOptionChange = EnterGame.onClientVersionChange })
enterGame:hide()
if g_app.isRunning() and not g_game.isOnline() then
@@ -146,6 +165,7 @@ end
function EnterGame.terminate()
g_keyboard.unbindKeyDown('Ctrl+G')
disconnect(clientBox, { onOptionChange = EnterGame.onClientVersionChange })
enterGame:destroy()
enterGame = nil
enterGameButton:destroy()
@@ -204,14 +224,80 @@ end
function EnterGame.clearAccountFields()
enterGame:getChildById('accountNameTextEdit'):clearText()
enterGame:getChildById('accountPasswordTextEdit'):clearText()
enterGame:getChildById('authenticatorTokenTextEdit'):clearText()
enterGame:getChildById('accountNameTextEdit'):focus()
g_settings.remove('account')
g_settings.remove('password')
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()
G.account = enterGame:getChildById('accountNameTextEdit'):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.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText())
local clientVersion = tonumber(clientBox:getText())
@@ -230,6 +316,7 @@ function EnterGame.doLogin()
protocolLogin = ProtocolLogin.create()
protocolLogin.onLoginError = onError
protocolLogin.onMotd = onMotd
protocolLogin.onSessionKey = onSessionKey
protocolLogin.onCharacterList = onCharacterList
protocolLogin.onUpdateNeeded = onUpdateNeeded
@@ -240,12 +327,12 @@ function EnterGame.doLogin()
EnterGame.show()
end })
g_game.chooseRsa(G.host)
g_game.setClientVersion(clientVersion)
g_game.setProtocolVersion(g_game.getClientProtocolVersion(clientVersion))
g_game.chooseRsa(G.host)
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
loadBox:destroy()
loadBox = nil
@@ -266,6 +353,7 @@ function EnterGame.setDefaultServer(host, port, protocol)
local clientLabel = enterGame:getChildById('clientLabel')
local accountTextEdit = enterGame:getChildById('accountNameTextEdit')
local passwordTextEdit = enterGame:getChildById('accountPasswordTextEdit')
local authenticatorTokenTextEdit = enterGame:getChildById('authenticatorTokenTextEdit')
if hostTextEdit:getText() ~= host then
hostTextEdit:setText(host)
@@ -273,6 +361,7 @@ function EnterGame.setDefaultServer(host, port, protocol)
clientBox:setCurrentOption(protocol)
accountTextEdit:setText('')
passwordTextEdit:setText('')
authenticatorTokenTextEdit:setText('')
end
end
@@ -286,6 +375,16 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
portTextEdit:setVisible(false)
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:setVisible(false)
clientBox:setHeight(0)
@@ -306,11 +405,11 @@ function EnterGame.setUniqueServer(host, port, protocol, windowWidth, windowHeig
serverListButton:setWidth(0)
local rememberPasswordBox = enterGame:getChildById('rememberPasswordBox')
rememberPasswordBox:setMarginTop(-5)
rememberPasswordBox:setMarginTop(-8)
if not windowWidth then windowWidth = 236 end
enterGame:setWidth(windowWidth)
if not windowHeight then windowHeight = 200 end
if not windowHeight then windowHeight = 210 end
enterGame:setHeight(windowHeight)
end

View File

@@ -1,6 +1,6 @@
EnterGameWindow < MainWindow
!text: tr('Enter Game')
size: 236 274
size: 236 298
EnterGameButton < Button
width: 64
@@ -21,6 +21,10 @@ ServerListButton < UIButton
EnterGameWindow
id: enterGame
&authenticatorEnabled: false
&authenticatorHeight: 44
&stayLoggedBoxEnabled: false
&stayLoggedBoxHeight: 24
@onEnter: EnterGame.doLogin()
MenuLabel
@@ -50,6 +54,52 @@ EnterGameWindow
anchors.top: prev.bottom
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
id: serverLabel
!text: tr('Server')
@@ -132,16 +182,24 @@ EnterGameWindow
anchors.top: prev.bottom
margin-top: 2
HorizontalSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: prev.bottom
margin-top: 6
EnterGameButton
!text: tr('Ok')
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: prev.bottom
margin-top: 4
@onClick: EnterGame.doLogin()
Label
id: serverInfoLabel
font: verdana-11px-rounded
anchors.bottom: parent.bottom
anchors.top: prev.top
anchors.left: parent.left
margin-top: 5
color: green
text-auto-resize: true

View File

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

View File

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

View File

@@ -12,7 +12,9 @@ function ServerList.init()
serverTextList = serverListWindow:getChildById('serverList')
servers = g_settings.getNode('ServerList') or {}
ServerList.load()
if servers then
ServerList.load()
end
end
function ServerList.terminate()
@@ -24,8 +26,8 @@ function ServerList.terminate()
end
function ServerList.load()
for k,server in pairs(servers) do
ServerList.add(k, server.port, server.protocol, true)
for host, server in pairs(servers) do
ServerList.add(host, server.port, server.protocol, true)
end
end
@@ -43,7 +45,9 @@ function ServerList.select()
end
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'
elseif host == '' or port == '' then
return false, 'Required fields are missing'

View File

@@ -30,6 +30,10 @@ local allLines = {}
-- private functions
local function navigateCommand(step)
if commandTextEdit:isMultiline() then
return
end
local numCommands = #commandHistory
if numCommands > 0 then
currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands)
@@ -96,16 +100,29 @@ local function completeCommand()
end
end
local function doCommand()
local currentCommand = commandTextEdit:getText()
local function doCommand(textWidget)
local currentCommand = textWidget:getText()
executeCommand(currentCommand)
if commandTextEdit then
commandTextEdit:clearText()
end
textWidget:clearText()
return true
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)
if disabled then return end
-- avoid logging while reporting logs (would cause a infinite loop)
@@ -129,6 +146,8 @@ function init()
commandHistory = g_settings.getList('terminal-history')
commandTextEdit = terminalWindow:getChildById('commandTextEdit')
commandTextEdit:setHeight(commandTextEdit.baseHeight)
connect(commandTextEdit, {onTextChange = onCommandChange})
g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit)
g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit)
g_keyboard.bindKeyPress('Ctrl+C',
@@ -138,6 +157,7 @@ function init()
return true
end, commandTextEdit)
g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit)
g_keyboard.bindKeyPress('Shift+Enter', addNewline, commandTextEdit)
g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit)
g_keyboard.bindKeyDown('Escape', hide, terminalWindow)
@@ -293,7 +313,7 @@ function addLine(text, color)
end
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
addLine("> " .. command, "#ffffff")

View File

@@ -47,7 +47,7 @@ UIWindow
anchors.left: parent.left
anchors.right: terminalScroll.left
anchors.top: terminalScroll.top
anchors.bottom: commandSymbolLabel.top
anchors.bottom: commandTextEdit.top
layout:
type: verticalBox
align-bottom: true
@@ -80,14 +80,25 @@ UIWindow
UITextEdit
id: commandTextEdit
height: 12
background: #aaaaaa11
border-color: #aaaaaa88
&baseHeight: 12
anchors.bottom: parent.bottom
anchors.left: commandSymbolLabel.right
anchors.right: parent.right
anchors.right: terminalScroll.left
margin-left: 1
padding-left: 2
font: terminus-10px
selection-color: black
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
id: bottomResizeBorder

View File

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

View File

@@ -8,6 +8,7 @@ function UIPopupMenu.create()
local layout = UIVerticalLayout.create(menu)
layout:setFitChildren(true)
menu:setLayout(layout)
menu.isGameMenu = false
return menu
end
@@ -34,6 +35,7 @@ function UIPopupMenu:display(pos)
rootWidget:addChild(self)
self:setPosition(pos)
self:grabMouse()
self:focus()
--self:grabKeyboard()
currentMenu = self
end
@@ -76,6 +78,10 @@ function UIPopupMenu:addSeparator()
g_ui.createWidget(self:getStyleName() .. 'Separator', self)
end
function UIPopupMenu:setGameMenu(state)
self.isGameMenu = state
end
function UIPopupMenu:onDestroy()
if currentMenu == self then
currentMenu = nil
@@ -105,4 +111,12 @@ local function onRootGeometryUpdate()
currentMenu:destroy()
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
end
local px = math.max(proportion * pxrange, 10)
local px = math.max(proportion * pxrange, 6)
px = px - px % 2 + 1
local offset = 0

View File

@@ -12,7 +12,7 @@ function UISpinBox.create()
spinbox.step = 1
spinbox.firstchange = true
spinbox.mouseScroll = true
spinbox:setText("0")
spinbox:setText("1")
spinbox:setValue(1)
return spinbox
end
@@ -66,7 +66,15 @@ function UISpinBox:onTextChange(text, oldText)
end
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
function UISpinBox:onStyleApply(styleName, styleNode)
@@ -109,14 +117,16 @@ function UISpinBox:down()
self:setValue(self.value - self.step)
end
function UISpinBox:setValue(value)
function UISpinBox:setValue(value, dontSignal)
value = value or 0
value = math.max(math.min(self.maximum, value), self.minimum)
if value == self.value then return end
self.value = value
if self:getText():len() > 0 then
self:setText(value)
end
self.value = value
local upButton = self:getChildById('up')
local downButton = self:getChildById('down')
@@ -127,7 +137,9 @@ function UISpinBox:setValue(value)
downButton:setEnabled(self.maximum ~= self.minimum and self.value ~= self.minimum)
end
signalcall(self.onValueChange, self, value)
if not dontSignal then
signalcall(self.onValueChange, self, value)
end
end
function UISpinBox:getValue()

View File

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

View File

@@ -3,33 +3,45 @@
TODO:
* Make table headers more robust.
* 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")
local HEADER_ID = 'row0'
-- Initialize default values
function UITable.create()
local table = UITable.internalCreate()
table.headerRow = nil
table.headerColumns = {}
table.dataSpace = nil
table.rows = {}
table.rowBaseStyle = nil
table.columns = {}
table.columnWidth = {}
table.columBaseStyle = nil
table.headerRowBaseStyle = nil
table.headerColumnBaseStyle = nil
table.selectedRow = nil
table.defaultColumnWidth = 80
table.sortColumn = -1
table.sortType = TABLE_SORTING_ASC
table.autoSort = false
return table
end
-- Clear table values
function UITable:onDestroy()
for k,row in pairs(self.rows) do
for _,row in pairs(self.rows) do
row.onClick = nil
end
self.rows = {}
self.columns = {}
self.headerRow = {}
self.headerRow = nil
self.headerColumns = {}
self.columnWidth = {}
self.selectedRow = nil
if self.dataSpace then
@@ -38,36 +50,58 @@ function UITable:onDestroy()
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)
for name, value in pairs(styleNode) do
if name == 'table-data' then
addEvent(function()
self:setTableData(self:getParent():getChildById(value))
end)
elseif name == 'column-style' then
addEvent(function()
self:setColumnStyle(value)
end)
elseif name == 'row-style' then
addEvent(function()
self:setRowStyle(value)
end)
elseif name == 'header-column-style' then
addEvent(function()
self:setHeaderColumnStyle(value)
end)
elseif name == 'header-row-style' then
addEvent(function()
self:setHeaderRowStyle(value)
end)
if value ~= false then
if name == 'table-data' then
addEvent(function()
self:setTableData(self:getParent():getChildById(value))
end)
elseif name == 'column-style' then
addEvent(function()
self:setColumnStyle(value)
end)
elseif name == 'row-style' then
addEvent(function()
self:setRowStyle(value)
end)
elseif name == 'header-column-style' then
addEvent(function()
self:setHeaderColumnStyle(value)
end)
elseif name == 'header-row-style' then
addEvent(function()
self:setHeaderRowStyle(value)
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()
return self.headerRow ~= nil
end
-- Clear all rows
function UITable:clearData()
if not self.dataSpace then
return
@@ -78,16 +112,42 @@ function UITable:clearData()
self.rows = {}
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
g_logger.error('UITable:addHeaderRow - table columns must be provided in a table')
return
end
self:removeHeader()
-- build header columns
local columns = {}
for _, column in pairs(data) do
for colId, column in pairs(data) do
local col = g_ui.createWidget(self.headerColumnBaseStyle)
col.colId = colId
col.table = self
for type, value in pairs(column) do
if type == 'width' then
col:setWidth(value)
@@ -104,26 +164,37 @@ function UITable:addHeaderRow(data)
-- create a new header
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 })
headerRow:setId(HEADER_ID)
headerRow:setId('header')
self.headerColumns = {}
self.columnWidth = {}
for _, column in pairs(columns) do
headerRow:addChild(column)
self.columns[HEADER_ID] = column
table.insert(self.columnWidth, column:getWidth())
table.insert(self.headerColumns, column)
end
headerRow.onClick = function(headerRow) self:selectRow(headerRow) end
self.headerRow = headerRow
return headerRow
end
function UITable:removeHeaderRow()
self.headerRow:destroy()
self.headerRow = nil
-- Remove header
function UITable:removeHeader()
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
function UITable:addRow(data, ref, height)
function UITable:addRow(data, height)
if not self.dataSpace then
g_logger.error('UITable:addRow - table data space has not been set, cannot add rows.')
return
@@ -134,41 +205,123 @@ function UITable:addRow(data, ref, height)
end
local row = g_ui.createWidget(self.rowBaseStyle)
if ref then row.ref = ref end
row.table = self
if height then row:setHeight(height) end
local rowId = #self.rows
row:setId('row'..(rowId < 1 and 1 or rowId))
local rowId = #self.rows + 1
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)
for type, value in pairs(column) do
if type == 'width' then
col:setWidth(value)
elseif type == 'height' then
col:setHeight(value)
elseif type == 'text' then
col:setText(value)
end
if column.width then
col:setWidth(column.width)
else
col:setWidth(self.columnWidth[colId] or self.defaultColumnWidth)
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
row.onFocusChange = function(row, focused)
if focused then self:selectRow(row) end
end
self.dataSpace:addChild(row)
table.insert(self.rows, row)
if self.autoSort then
self:sort()
end
return row
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)
if self.selectedRow == row then
self:selectRow(nil)
end
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
function UITable:selectRow(selectedRow)
@@ -189,21 +342,34 @@ function UITable:selectRow(selectedRow)
end
function UITable:setTableData(tableData)
local headerHeight = 0
if self.headerRow then
headerHeight = self.headerRow:getHeight()
end
self.dataSpace = tableData
self.dataSpace:applyStyle({ height = self:getHeight() })
self.dataSpace:applyStyle({ height = self:getHeight()-headerHeight-self:getMarginTop() })
end
function UITable:setRowStyle(style)
function UITable:setRowStyle(style, dontUpdate)
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
function UITable:setColumnStyle(style)
function UITable:setColumnStyle(style, dontUpdate)
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
@@ -216,7 +382,51 @@ end
function UITable:setHeaderColumnStyle(style)
self.headerColumnBaseStyle = style
if table.haskey(HEADER_ID) then
self.columns[HEADER_ID]:setStyle(style)
for _, col in pairs(self.headerColumns) do
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

View File

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

View File

@@ -15,7 +15,8 @@ fightModeRadioGroup = nil
pvpModeRadioGroup = nil
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)
combatControlsWindow = g_ui.loadUI('combatcontrols', modules.game_interface.getRightPanel())
combatControlsWindow:disableResize()

View File

@@ -3,7 +3,7 @@ SpeakTypesSettings = {
say = { speakType = MessageModes.Say, color = '#FFFF00' },
whisper = { speakType = MessageModes.Whisper, 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 },
privateRed = { speakType = MessageModes.GamemasterTo, color = '#F55E5E', private = true },
privatePlayerToPlayer = { speakType = MessageModes.PrivateTo, color = '#9F9DFD', private = true },
@@ -38,6 +38,7 @@ SpeakTypes = {
[MessageModes.RVRChannel] = SpeakTypesSettings.channelWhite,
[MessageModes.RVRContinue] = SpeakTypesSettings.rvrContinue,
[MessageModes.RVRAnswer] = SpeakTypesSettings.rvrAnswerFrom,
[MessageModes.NpcFromStartBlock] = SpeakTypesSettings.privateNpcToPlayer,
-- ignored types
[MessageModes.Spell] = SpeakTypesSettings.none,
@@ -105,7 +106,6 @@ function init()
consoleContentPanel = consolePanel:getChildById('consoleContentPanel')
consoleTabBar = consolePanel:getChildById('consoleTabBar')
consoleTabBar:setContentWidget(consoleContentPanel)
consoleTabBar:setTabSpacing(-1)
channels = {}
consolePanel.onKeyPress = function(self, keyCode, keyboardModifiers)
@@ -114,13 +114,10 @@ function init()
local tab = consoleTabBar:getCurrentTab()
if not tab then return false end
local consoleBuffer = tab.tabPanel:getChildById('consoleBuffer')
if not consoleBuffer then return false end
local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText
if not selection then return false end
local consoleLabel = consoleBuffer:getFocusedChild()
if not consoleLabel or not consoleLabel:hasSelection() then return false end
g_window.setClipboardText(consoleLabel:getSelection())
g_window.setClipboardText(selection)
return true
end
@@ -148,6 +145,27 @@ function init()
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()
if consoleToggleChat:isChecked() then
disableChat()
@@ -164,12 +182,18 @@ function enableChat()
g_keyboard.unbindKeyUp("Space")
g_keyboard.unbindKeyUp("Enter")
g_keyboard.unbindKeyUp("Escape")
gameInterface.unbindWalkKey("W")
gameInterface.unbindWalkKey("D")
gameInterface.unbindWalkKey("S")
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"))
end
@@ -187,12 +211,18 @@ function disableChat()
end
g_keyboard.bindKeyUp("Space", quickFunc)
g_keyboard.bindKeyUp("Enter", quickFunc)
g_keyboard.bindKeyUp("Escape", quickFunc)
gameInterface.bindWalkKey("W", North)
gameInterface.bindWalkKey("D", East)
gameInterface.bindWalkKey("S", South)
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"))
end
@@ -233,7 +263,13 @@ function terminate()
violationWindow:destroy()
end
consoleTabBar = nil
consoleContentPanel = nil
consoleToggleChat = nil
consoleTextEdit = nil
consolePanel:destroy()
consolePanel = nil
ownPrivateName = nil
Console = nil
@@ -288,11 +324,14 @@ function clear()
channels = {}
consoleTabBar:removeTab(defaultTab)
defaultTab = nil
consoleTabBar:removeTab(serverTab)
serverTab = nil
local npcTab = consoleTabBar:getTab('NPCs')
if npcTab then
consoleTabBar:removeTab(npcTab)
npcTab = nil
end
if violationReportTab then
@@ -557,12 +596,102 @@ function addTabText(text, speaktype, tab, creatureName)
end
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)
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
consoleBuffer:getFirstChild():destroy()
local child = consoleBuffer:getFirstChild()
clearSelection(consoleBuffer)
child:destroy()
end
end
@@ -578,6 +707,7 @@ end
function processChannelTabMenu(tab, mousePos, mouseButton)
local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
channelName = tab:getText()
if tab ~= defaultTab and tab ~= serverTab then
@@ -588,8 +718,28 @@ function processChannelTabMenu(tab, mousePos, mouseButton)
if consoleTabBar:getCurrentTab() == tab then
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
--menu:addOption(tr('Save Messages'), function() --[[TODO]] end)
menu:display(mousePos)
end
@@ -597,6 +747,7 @@ end
function processMessageMenu(mousePos, mouseButton, creatureName, text, label, tab)
if mouseButton == MouseRightButton then
local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
if creatureName and #creatureName > 0 then
if creatureName ~= g_game.getCharacterName() then
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)
end
if label:hasSelection() then
menu:addOption(tr('Copy'), function() g_window.setClipboardText(label:getSelection()) end, '(Ctrl+C)')
local selection = tab.tabPanel:getChildById('consoleBuffer').selectionText
if selection and #selection > 0 then
menu:addOption(tr('Copy'), function() g_window.setClipboardText(selection) end, '(Ctrl+C)')
end
menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end)
menu:addOption(tr('Select all'), function() label:selectAll() end)
if tab.violations then
if text then
menu:addOption(tr('Copy message'), function() g_window.setClipboardText(text) end)
end
menu:addOption(tr('Select all'), function() selectAll(tab.tabPanel:getChildById('consoleBuffer')) end)
if tab.violations and creatureName then
menu:addSeparator()
menu:addOption(tr('Process') .. ' ' .. creatureName, function() processViolation(creatureName, text) end)
menu:addOption(tr('Remove') .. ' ' .. creatureName, function() g_game.closeRuleViolation(creatureName) end)
@@ -687,7 +841,7 @@ function sendMessage(message, tab)
end
-- player used whisper
local chatCommandMessage = message:match("^%#[w|W] (.*)")
chatCommandMessage = message:match("^%#[w|W] (.*)")
if chatCommandMessage ~= nil then
chatCommandSayMode = 'whisper'
message = chatCommandMessage
@@ -695,13 +849,28 @@ function sendMessage(message, tab)
end
-- player say
local chatCommandMessage = message:match("^%#[s|S] (.*)")
chatCommandMessage = message:match("^%#[s|S] (.*)")
if chatCommandMessage ~= nil then
chatCommandSayMode = 'say'
message = chatCommandMessage
channel = 0
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("([%*%@])(.+)([%*%@])(.*)")
if findIni ~= nil and findIni == 1 then -- player used private chat command
if chatCommandInitial == chatCommandEnd then
@@ -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
mode == MessageModes.Spell or mode == MessageModes.MonsterSay or mode == MessageModes.MonsterYell or
mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud) and
creaturePos then
-- Remove curly braces from screen message
local staticMessage = message
if mode == MessageModes.NpcFrom then
local highlightData = getHighlightedText(staticMessage)
if #highlightData > 0 then
for i = 1, #highlightData / 3 do
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words)
end
mode == MessageModes.NpcFrom or mode == MessageModes.BarkLow or mode == MessageModes.BarkLoud or
mode == MessageModes.NpcFromStartBlock) and creaturePos then
local staticText = StaticText.create()
-- Remove curly braces from screen message
local staticMessage = message
if mode == MessageModes.NpcFrom or mode == MessageModes.NpcFromStartBlock then
local highlightData = getHighlightedText(staticMessage)
if #highlightData > 0 then
for i = 1, #highlightData / 3 do
local dataBlock = { _start = highlightData[(i-1)*3+1], _end = highlightData[(i-1)*3+2], words = highlightData[(i-1)*3+3] }
staticMessage = staticMessage:gsub("{"..dataBlock.words.."}", dataBlock.words)
end
end
local staticText = StaticText.create()
staticText:setColor(speaktype.color)
end
staticText:addMessage(name, mode, staticMessage)
g_map.addThing(staticText, creaturePos, -1)
end

View File

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

View File

@@ -27,6 +27,15 @@ function init()
onLoginAdvice = onLoginAdvice,
}, 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:hide()
gameRootPanel:lower()
@@ -49,7 +58,6 @@ function init()
setupViewMode(0)
bindKeys()
load()
if g_game.isOnline() then
show()
@@ -102,7 +110,6 @@ function unbindWalkKey(key)
end
function terminate()
save()
hide()
hookedMenuOptions = {}
@@ -442,7 +449,10 @@ end
function createThingMenu(menuPosition, lookThing, useThing, creatureThing)
if not g_game.isOnline() then return end
local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
local classic = modules.client_options.getOption('classicControl')
local shortcut = nil

View File

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

View File

@@ -17,7 +17,10 @@ inventoryButton = nil
purseButton = nil
function init()
connect(LocalPlayer, { onInventoryChange = onInventoryChange })
connect(LocalPlayer, {
onInventoryChange = onInventoryChange,
onBlessingsChange = onBlessingsChange
})
connect(g_game, { onGameStart = refresh })
g_keyboard.bindKeyDown('Ctrl+I', toggle)
@@ -43,7 +46,10 @@ function init()
end
function terminate()
disconnect(LocalPlayer, { onInventoryChange = onInventoryChange })
disconnect(LocalPlayer, {
onInventoryChange = onInventoryChange,
onBlessingsChange = onBlessingsChange
})
disconnect(g_game, { onGameStart = refresh })
g_keyboard.unbindKeyDown('Ctrl+I')
@@ -52,6 +58,15 @@ function terminate()
inventoryButton:destroy()
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()
local player = g_game.getLocalPlayer()
for i = InventorySlotFirst, InventorySlotPurse do
@@ -60,6 +75,7 @@ function refresh()
else
onInventoryChange(player, i, nil)
end
toggleAdventurerStyle(player and Bit.hasBit(player:getBlessings(), Blessings.Adventurer) or false)
end
purseButton:setVisible(g_game.getFeature(GamePurseSlot))
@@ -92,10 +108,17 @@ function onInventoryChange(player, slot, item, oldItem)
local itemWidget = inventoryPanel:getChildById('slot' .. slot)
if item then
itemWidget:setStyle('Item')
itemWidget:setStyle('InventoryItem')
itemWidget:setItem(item)
else
itemWidget:setStyle(InventorySlotStyles[slot])
itemWidget:setItem(nil)
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
$on:
image-source: /images/ui/item-blessed
HeadSlot < InventoryItem
id: slot1
image-source: /images/game/slots/head
&position: {x=65535, y=1, z=0}
$on:
image-source: /images/game/slots/head-blessed
BodySlot < InventoryItem
id: slot4
image-source: /images/game/slots/body
&position: {x=65535, y=4, z=0}
$on:
image-source: /images/game/slots/body-blessed
LegSlot < InventoryItem
id: slot7
image-source: /images/game/slots/legs
&position: {x=65535, y=7, z=0}
$on:
image-source: /images/game/slots/legs-blessed
FeetSlot < InventoryItem
id: slot8
image-source: /images/game/slots/feet
&position: {x=65535, y=8, z=0}
$on:
image-source: /images/game/slots/feet-blessed
NeckSlot < InventoryItem
id: slot2
image-source: /images/game/slots/neck
&position: {x=65535, y=2, z=0}
$on:
image-source: /images/game/slots/neck-blessed
LeftSlot < InventoryItem
id: slot6
image-source: /images/game/slots/left-hand
&position: {x=65535, y=6, z=0}
$on:
image-source: /images/game/slots/left-hand-blessed
FingerSlot < InventoryItem
id: slot9
image-source: /images/game/slots/finger
&position: {x=65535, y=9, z=0}
$on:
image-source: /images/game/slots/finger-blessed
BackSlot < InventoryItem
id: slot3
image-source: /images/game/slots/back
&position: {x=65535, y=3, z=0}
$on:
image-source: /images/game/slots/back-blessed
RightSlot < InventoryItem
id: slot5
image-source: /images/game/slots/right-hand
&position: {x=65535, y=5, z=0}
$on:
image-source: /images/game/slots/right-hand-blessed
AmmoSlot < InventoryItem
id: slot10
image-source: /images/game/slots/ammo
&position: {x=65535, y=10, z=0}
$on:
image-source: /images/game/slots/ammo-blessed
PurseButton < Button
id: purseButton

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ MarketOffer.__index = MarketOffer
local OFFER_TIMESTAMP = 1
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 = {
id = {},
type = nil,
@@ -12,7 +12,8 @@ MarketOffer.new = function(offerId, t, item, amount, price, playerName, state)
amount = 0,
price = 0,
player = '',
state = 0
state = 0,
var = nil
}
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.')
end
offer.state = state
offer.var = var
setmetatable(offer, MarketOffer)
return offer

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,35 +1,27 @@
OfferTableRow < TableRow
font: verdana-11px-monochrome
background-color: alpha
focusable: true
color: #cccccc
height: 15
$focus:
background-color: #294f6d
color: #ffffff
OfferTableColumn < TableColumn
font: verdana-11px-monochrome
background-color: alpha
text-offset: 5 0
color: #cccccc
width: 80
focusable: false
OfferTableWarningColumn < OfferTableColumn
color: #e03d3d
OfferTableHeaderRow < TableHeaderRow
font: verdana-11px-monochrome
focusable: false
color: #cccccc
height: 20
OfferTableHeaderColumn < TableHeaderColumn
OfferTableHeaderColumn < SortableTableHeaderColumn
font: verdana-11px-monochrome
background-color: alpha
text-offset: 2 0
color: #cccccc
width: 80
focusable: true
$focus:
background-color: #294f6d
@@ -74,8 +66,26 @@ Panel
table-data: sellingTableData
row-style: OfferTableRow
column-style: OfferTableColumn
header-row-style: OfferTableHeaderRow
header-column-style: OfferTableHeaderColumn
header-column-style: false
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
id: sellingTableData
@@ -129,8 +139,26 @@ Panel
table-data: buyingTableData
row-style: OfferTableRow
column-style: OfferTableColumn
header-row-style: OfferTableHeaderRow
header-column-style: OfferTableHeaderColumn
header-column-style: false
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
id: buyingTableData

View File

@@ -1,6 +1,5 @@
StatsTableRow < TableRow
font: verdana-11px-monochrome
background-color: alpha
focusable: true
color: #cccccc
height: 20
@@ -9,7 +8,7 @@ StatsTableRow < TableRow
StatsTableColumn < TableColumn
font: verdana-11px-monochrome
background-color: alpha
text-offset: 5 0
text-offset: 5 3
color: #cccccc
width: 110
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
background-color: #22283399
margin: 1
Button
id: sellCancelButton
!text: tr('Cancel')
anchors.right: parent.right
anchors.bottom: next.bottom
margin-right: 6
width: 80
enabled: false
Label
!text: tr('Current Offers')
!text: tr('Sell Offers')
font: verdana-11px-rounded
text-offset: 0 2
anchors.top: parent.top
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 choiceList = modalDialog:getChildById('choiceList')
local choiceScrollbar = modalDialog:getChildById('choiceScrollBar')
local buttonList = modalDialog:getChildById('buttonList')
local buttonsPanel = modalDialog:getChildById('buttonsPanel')
modalDialog:setText(title)
messageLabel:setText(message)
local horizontalPadding = modalDialog:getPaddingLeft() + modalDialog:getPaddingRight()
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
local labelHeight
for i = 1, #choices do
local choiceId = choices[i][1]
local choiceName = choices[i][2]
@@ -59,11 +55,12 @@ function onModalDialog(id, title, message, buttons, enterButton, escapeButton, c
end
choiceList:focusNextChild()
local buttonsWidth = 0
for i = 1, #buttons do
local buttonId = buttons[i][1]
local buttonText = buttons[i][2]
local button = g_ui.createWidget('ModalButton', buttonList)
local button = g_ui.createWidget('ModalButton', buttonsPanel)
button:setText(buttonText)
button.onClick = function(self)
local focusedChoice = choiceList:getFocusedChild()
@@ -74,6 +71,7 @@ function onModalDialog(id, title, message, buttons, enterButton, escapeButton, c
g_game.answerModalDialog(id, buttonId, choice)
destroyDialog()
end
buttonsWidth = buttonsWidth + button:getWidth() + button:getMarginLeft() + button:getMarginRight()
end
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 = additionalHeight + choiceList:getPaddingTop() + choiceList:getPaddingBottom()
end
modalDialog:setHeight(modalDialog:getHeight() + additionalHeight)
addEvent(function()
modalDialog:setHeight(modalDialog:getHeight() + messageLabel:getHeight() - 14)
end)
local horizontalPadding = modalDialog:getPaddingLeft() + modalDialog:getPaddingRight()
buttonsWidth = buttonsWidth + horizontalPadding
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 focusedChoice = choiceList:getFocusedChild()

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
DeathWindow < MainWindow
id: deathWindow
!text: tr('You are dead')
size: 350 155
&baseWidth: 350
&baseHeight: 15
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
height: 140
anchors.left: parent.left

View File

@@ -1,5 +1,11 @@
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()
g_ui.importStyle('deathwindow')
@@ -21,9 +27,9 @@ function reset()
end
end
function display()
function display(deathType, penalty)
displayDeadMessage()
openWindow()
openWindow(deathType, penalty)
end
function displayDeadMessage()
@@ -33,12 +39,31 @@ function displayDeadMessage()
modules.game_textmessage.displayGameMessage(tr('You are dead.'))
end
function openWindow()
function openWindow(deathType, penalty)
if deathWindow then
deathWindow:destroy()
return
end
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 cancelButton = deathWindow:getChildById('buttonCancel')

View File

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

View File

@@ -8,6 +8,7 @@ TextMessageLabel < UILabel
Panel
anchors.fill: gameMapPanel
anchors.bottom: gameBottomPanel.top
focusable: false
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
function createAddWindow()
addVipWindow = g_ui.displayUI('addvip')
if not addVipWindow then
addVipWindow = g_ui.displayUI('addvip')
end
end
function createEditWindow(widget)
@@ -343,6 +345,7 @@ function onVipListMousePress(widget, mousePos, mouseButton)
local vipList = vipWindow:getChildById('contentsPanel')
local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
menu:addOption(tr('Add new VIP'), function() createAddWindow() end)
menu:addSeparator()
@@ -375,6 +378,7 @@ function onVipListLabelMousePress(widget, mousePos, mouseButton)
local vipList = vipWindow:getChildById('contentsPanel')
local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
menu:addOption(tr('Send Message'), function() g_game.openPrivateChannel(widget:getText()) 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)

View File

@@ -120,6 +120,17 @@ GameSpritesAlphaChannel = 56
GamePremiumExpiration = 57
GameBrowseField = 58
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 = {
red = '#f55e5e', --'#c83200'
@@ -184,7 +195,9 @@ MessageModes = {
RVRChannel = 46,
RVRAnswer = 47,
RVRContinue = 48,
Last = 49,
GameHighlight = 49,
NpcFromStartBlock = 50,
Last = 51,
Invalid = 255,
}
@@ -201,7 +214,7 @@ CIPSOFT_RSA = "1321277432058722840622950990822933849527763264961655079678763618"
"88792221429527047321331896351555606801473202394175817"
-- set to the latest Tibia.pic signature to make otclient compatible with official tibia
PIC_SIGNATURE = 0x53208400
PIC_SIGNATURE = 0x542100C1
OsTypes = {
Linux = 1,
@@ -244,4 +257,25 @@ ExtendedIds = {
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)
local path
if skullId == SkullYellow then

View File

@@ -1,7 +1,5 @@
local currentRsa
function g_game.getRsa()
return currentRsa
return G.currentRsa
end
function g_game.findPlayerItem(itemId, subType)
@@ -19,7 +17,7 @@ function g_game.findPlayerItem(itemId, subType)
end
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
g_game.setRsa(CIPSOFT_RSA)
@@ -29,21 +27,26 @@ function g_game.chooseRsa(host)
g_game.setCustomOs(OsTypes.Linux)
end
else
if currentRsa == CIPSOFT_RSA then
if G.currentRsa == CIPSOFT_RSA then
g_game.setCustomOs(-1)
end
g_game.setRsa(OTSERV_RSA)
end
-- Hack fix to resolve some 760 login issues
if g_game.getClientVersion() <= 760 then
g_game.setCustomOs(2)
end
end
function g_game.setRsa(rsa, e)
e = e or '65537'
g_crypt.rsaSetPublicKey(rsa, e)
currentRsa = rsa
G.currentRsa = rsa
end
function g_game.isOfficialTibia()
return currentRsa == CIPSOFT_RSA
return G.currentRsa == CIPSOFT_RSA
end
function g_game.getSupportedClients()
@@ -68,7 +71,9 @@ function g_game.getSupportedClients()
1035, 1036, 1037, 1038, 1039,
1040, 1041, 1050, 1051, 1052,
1053, 1054, 1055, 1056, 1057,
1058, 1059, 1060, 1061
1058, 1059, 1060, 1061, 1062,
1063, 1064, 1070, 1071, 1072,
1073, 1074, 1075, 1076
}
end
@@ -105,4 +110,6 @@ function g_game.getClientProtocolVersion(client)
return clients[client] or client
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 'player'
dofile 'market'
dofile 'textmessages'
dofile 'thing'
dofile 'spells'

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