Initial commit
This commit is contained in:
0
pp/__init__.py
Normal file
0
pp/__init__.py
Normal file
104
pp/catch_the_pp/.gitignore
vendored
Normal file
104
pp/catch_the_pp/.gitignore
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
.idea/
|
||||
*.c
|
674
pp/catch_the_pp/LICENSE
Normal file
674
pp/catch_the_pp/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
39
pp/catch_the_pp/README.md
Normal file
39
pp/catch_the_pp/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Cythonized catch-the-pp
|
||||
An osu ctb gamemode star/pp calculator made in Cython.
|
||||
Original repo: [catch-the-pp](https://github.com/osufx/catch-the-pp) by [Sunpy](https://github.com/EmilySunpy).
|
||||
*Note: This repo is meant to be used as a Python package, not as a standalone program!*
|
||||
|
||||
## Changes
|
||||
- Cythonized all files, functions, classes and methods (with static typing as well)
|
||||
- Replaced `math.pow` with `**`, this gives _a bit_ of extra speed
|
||||
- Replaced imports
|
||||
- Minor code cleaning
|
||||
|
||||
## Performance
|
||||
These are the execution times after running pp calculation (with beatmap parsing and difficulty calculation as well) on `reanimate.osu` 100 times
|
||||
Pure Python version: `Min: 0.7986021041870117 s, Max: 0.932903528213501 s, Avg: 0.8350819730758667 s`
|
||||
Cythonized version: `Min: 0.22933077812194824 s, Max: 0.25774192810058594 s, Avg: 0.23836223363876344 s`
|
||||
|
||||
|
||||
## Compiling & Usage
|
||||
```
|
||||
$ git clone ... catch_the_pp
|
||||
$ cd catch_the_pp
|
||||
$ python3.6 setup.py build_ext --inplace
|
||||
...
|
||||
$ cd ..
|
||||
$ python3.6 -m catch_the_pp.sample
|
||||
Calculation:
|
||||
Stars: 1.9046727418899536, PP: 42.187660217285156, MaxCombo: 1286
|
||||
$ python3.6
|
||||
>>> from catch_the_pp.osu_parser.beatmap import Beatmap
|
||||
>>> from catch_the_pp.osu.ctb.difficulty import Difficulty
|
||||
>>> from catch_the_pp.ppCalc import calculate_pp
|
||||
>>> beatmap = Beatmap("catch_the_pp/test.osu")
|
||||
>>> difficulty = Difficulty(beatmap=beatmap, mods=0)
|
||||
>>> difficulty.star_rating
|
||||
1.9046727418899536
|
||||
>>> calculate_pp(diff=difficulty, accuracy=1, combo=beatmap.max_combo, miss=0)
|
||||
42.187660217285156
|
||||
```
|
||||
> Note: You must clone the repo in a folder that has no dashes in its name, because Python modules cannot have dashes in their name! In this example, `catch_the_pp` was used.
|
0
pp/catch_the_pp/__init__.py
Normal file
0
pp/catch_the_pp/__init__.py
Normal file
9
pp/catch_the_pp/constants.pyx
Normal file
9
pp/catch_the_pp/constants.pyx
Normal file
@@ -0,0 +1,9 @@
|
||||
STAR_SCALING_FACTOR = 0.145
|
||||
STRAIN_STEP = 750
|
||||
DECAY_WEIGHT = 0.94
|
||||
DECAY_BASE = 0.2
|
||||
ABSOLUTE_PLAYER_POSITIONING_ERROR = 16
|
||||
NORMALIZED_HITOBJECT_RADIUS = 41
|
||||
DIRECTION_CHANGE_BONUS = 12.5
|
||||
|
||||
SLIDER_QUALITY = 50
|
0
pp/catch_the_pp/osu/__init__.py
Normal file
0
pp/catch_the_pp/osu/__init__.py
Normal file
0
pp/catch_the_pp/osu/ctb/__init__.py
Normal file
0
pp/catch_the_pp/osu/ctb/__init__.py
Normal file
268
pp/catch_the_pp/osu/ctb/difficulty.pyx
Normal file
268
pp/catch_the_pp/osu/ctb/difficulty.pyx
Normal file
@@ -0,0 +1,268 @@
|
||||
from ... import constants
|
||||
from ...osu_parser.mathhelper import clamp, sign
|
||||
|
||||
|
||||
cdef class DifficultyObject:
|
||||
"""
|
||||
Object that holds strain value etc.
|
||||
|
||||
Handled in Difficulty.calculate_strainValues & Difficulty.update_hyperdash_distance.
|
||||
Used in Difficulty.calculate_difficulty
|
||||
"""
|
||||
cdef public float strain, last_movement
|
||||
cdef public float offset, player_width, scaled_position, hyperdash_distance
|
||||
cdef public object hitobject
|
||||
cdef public int error_margin, hyperdash
|
||||
|
||||
def __init__(self, hitobject, player_width):
|
||||
"""
|
||||
Hitobject wrapper to do calculation with.
|
||||
|
||||
hitobject -- Hitobject to wrap around (basic)
|
||||
player_width -- Catcher width (after determined by active mods)
|
||||
"""
|
||||
self.strain = 1
|
||||
self.offset = 0
|
||||
self.last_movement = 0
|
||||
self.hitobject = hitobject
|
||||
self.error_margin = constants.ABSOLUTE_PLAYER_POSITIONING_ERROR
|
||||
self.player_width = player_width
|
||||
self.scaled_position = self.hitobject.x * (constants.NORMALIZED_HITOBJECT_RADIUS / self.player_width)
|
||||
self.hyperdash_distance = 0
|
||||
self.hyperdash = False
|
||||
|
||||
cpdef calculate_strain(self, object last, float time_rate):
|
||||
"""
|
||||
Calculate strain value by refering last object.
|
||||
(and sets offset & last_movement info)
|
||||
|
||||
last -- Previous hitobject
|
||||
time_rate -- Timescale from enabled mods
|
||||
"""
|
||||
cdef float time = (self.hitobject.time - last.hitobject.time) / time_rate
|
||||
cdef float decay = constants.DECAY_BASE ** (time / 1000)
|
||||
|
||||
self.offset = clamp(last.scaled_position + last.offset,
|
||||
self.scaled_position - (constants.NORMALIZED_HITOBJECT_RADIUS - self.error_margin),
|
||||
self.scaled_position + (constants.NORMALIZED_HITOBJECT_RADIUS - self.error_margin)
|
||||
) - self.scaled_position
|
||||
|
||||
self.last_movement = abs(self.scaled_position - last.scaled_position + self.offset - last.offset)
|
||||
|
||||
cdef float addition = (self.last_movement ** 1.3) / 500
|
||||
|
||||
if self.scaled_position < last.scaled_position:
|
||||
self.last_movement *= -1
|
||||
|
||||
cdef float addition_bonus = 0
|
||||
cdef float sqrt_time = max(time, 25) ** 0.5
|
||||
|
||||
if abs(self.last_movement) > 0.1:
|
||||
if abs(last.last_movement) > 0.1 and sign(self.last_movement) != sign(last.last_movement):
|
||||
bonus = constants.DIRECTION_CHANGE_BONUS / sqrt_time
|
||||
bonus_factor = min(self.error_margin, abs(self.last_movement)) / self.error_margin
|
||||
|
||||
addition += bonus * bonus_factor
|
||||
|
||||
if last.hyperdash_distance <= 10:
|
||||
addition_bonus += 0.3 * bonus_factor
|
||||
|
||||
addition += 7.5 * min(abs(self.last_movement), constants.NORMALIZED_HITOBJECT_RADIUS * 2) / (constants.NORMALIZED_HITOBJECT_RADIUS * 6) / sqrt_time
|
||||
|
||||
if last.hyperdash_distance <= 10:
|
||||
if not last.hyperdash:
|
||||
addition_bonus += 1
|
||||
else:
|
||||
self.offset = 0
|
||||
|
||||
addition *= 1 + addition_bonus * ((10 - last.hyperdash_distance) / 10)
|
||||
|
||||
addition *= 850 / max(time, 25)
|
||||
self.strain = last.strain * decay + addition
|
||||
|
||||
cdef class Difficulty:
|
||||
"""
|
||||
Difficulty object for calculating star rating.
|
||||
|
||||
Stars: self.star_rating
|
||||
"""
|
||||
cdef public object beatmap
|
||||
cdef public int mods
|
||||
cdef public list hitobjects_with_ticks, difficulty_objects
|
||||
cdef public float time_rate, player_width, star_rating
|
||||
|
||||
|
||||
def __init__(self, beatmap, mods):
|
||||
"""
|
||||
CTB difficulty calculator params.
|
||||
Calculates the star rating for the given beatmap.
|
||||
|
||||
beatmap -- Beatmap object of parsed beatmap
|
||||
mods -- Int representation of mods selected / bitmask
|
||||
"""
|
||||
self.beatmap = beatmap
|
||||
self.mods = mods
|
||||
|
||||
#Difficulty modifier by mod
|
||||
cdef str diff
|
||||
for diff in self.beatmap.difficulty.keys():
|
||||
if diff == "CircleSize":
|
||||
scala = 1.3
|
||||
else:
|
||||
scala = 1.4
|
||||
self.beatmap.difficulty[diff] = self.adjust_difficulty(self.beatmap.difficulty[diff], self.mods, scala)
|
||||
|
||||
cdef object hitobject
|
||||
self.hitobjects_with_ticks = []
|
||||
for hitobject in self.beatmap.hitobjects:
|
||||
self.hitobjects_with_ticks.append(hitobject)
|
||||
if 2 & hitobject.type:
|
||||
for tick in hitobject.ticks:
|
||||
self.hitobjects_with_ticks.append(tick)
|
||||
for end_tick in hitobject.end_ticks:
|
||||
self.hitobjects_with_ticks.append(end_tick)
|
||||
|
||||
self.difficulty_objects = []
|
||||
|
||||
#Do the calculation
|
||||
self.time_rate = self.get_time_rate()
|
||||
self.player_width = 305 / 1.6 * ((102.4 * (1 - 0.7 * (self.beatmap.difficulty["CircleSize"] - 5) / 5)) / 128) * 0.7
|
||||
|
||||
for hitobject in self.hitobjects_with_ticks:
|
||||
self.difficulty_objects.append(DifficultyObject(hitobject, self.player_width * 0.4))
|
||||
|
||||
self.update_hyperdash_distance()
|
||||
|
||||
#Sort the list so its sorted by time (Incase it somehow isnt)
|
||||
self.difficulty_objects.sort(key=lambda o: o.hitobject.time)
|
||||
|
||||
self.calculate_strain_values()
|
||||
|
||||
self.star_rating = (self.calculate_difficulty() ** 0.5) * constants.STAR_SCALING_FACTOR
|
||||
|
||||
|
||||
def adjust_difficulty(self, diff, mods, scala):
|
||||
"""
|
||||
Scale difficulty from selected mods.
|
||||
|
||||
diff -- CircleSize
|
||||
mods -- Int representation of mods selected / bitmask
|
||||
return -- Scaled difficulty
|
||||
"""
|
||||
if mods & 1 << 1 > 0: #EZ
|
||||
diff = max(0, diff / 2)
|
||||
if mods & 1 << 4 > 0: #HR
|
||||
diff = min(10, diff * scala)
|
||||
|
||||
return diff
|
||||
|
||||
def get_time_rate(self):
|
||||
"""
|
||||
Get scaled time_rate from mods. (DT / HT)
|
||||
|
||||
return -- time_rate
|
||||
"""
|
||||
rate = 1
|
||||
|
||||
if self.mods & 1 << 6 > 0: #DT
|
||||
rate += 0.5
|
||||
elif self.mods & 1 << 8 > 0: #HT
|
||||
rate -= 0.25
|
||||
|
||||
return rate
|
||||
|
||||
cpdef update_hyperdash_distance(self):
|
||||
"""
|
||||
Update hyperdash_distance value for every hitobject in the beatmap.
|
||||
"""
|
||||
cdef int last_direction = 0, direction, i
|
||||
cdef float player_width_half = self.player_width / 2
|
||||
cdef float last = player_width_half
|
||||
|
||||
cdef object current_object, next_object
|
||||
|
||||
for i in range(len(self.difficulty_objects) - 1):
|
||||
current_object = self.difficulty_objects[i]
|
||||
next_object = self.difficulty_objects[i + 1]
|
||||
|
||||
if next_object.hitobject.x > current_object.hitobject.x:
|
||||
direction = 1
|
||||
else:
|
||||
direction = -1
|
||||
|
||||
time_to_next = next_object.hitobject.time - current_object.hitobject.time - 4.166667 #ms for 60fps divided by 4
|
||||
distance_to_next = abs(next_object.hitobject.x - current_object.hitobject.x)
|
||||
if last_direction == direction:
|
||||
distance_to_next -= last
|
||||
else:
|
||||
distance_to_next -= player_width_half
|
||||
|
||||
if time_to_next < distance_to_next:
|
||||
current_object.hyperdash = True
|
||||
last = player_width_half
|
||||
else:
|
||||
current_object.hyperdash_distance = time_to_next - distance_to_next
|
||||
last = clamp(current_object.hyperdash_distance, 0, player_width_half)
|
||||
|
||||
last_direction = direction
|
||||
|
||||
cpdef calculate_strain_values(self):
|
||||
"""
|
||||
Calculate strain values for every hitobject.
|
||||
|
||||
It does this by using distance, decay & previous hitobject strain value.
|
||||
Time_rate also effects this.
|
||||
"""
|
||||
cdef object current_object = self.difficulty_objects[0], next_object
|
||||
|
||||
cdef index = 1
|
||||
while index < len(self.difficulty_objects):
|
||||
next_object = self.difficulty_objects[index]
|
||||
next_object.calculate_strain(current_object, self.time_rate)
|
||||
current_object = next_object
|
||||
index += 1
|
||||
|
||||
cpdef float calculate_difficulty(self):
|
||||
"""
|
||||
Calculates the difficulty for this beatmap.
|
||||
This is used in the final function to calculate star rating.
|
||||
DISCLAIMER: This is not the final star rating value.
|
||||
|
||||
return -- difficulty
|
||||
"""
|
||||
cdef float strain_step = constants.STRAIN_STEP * self.time_rate
|
||||
cdef list highest_strains = []
|
||||
cdef float interval = strain_step
|
||||
cdef float max_strain = 0
|
||||
|
||||
cdef object last = None, difficulty_object
|
||||
|
||||
for difficulty_object in self.difficulty_objects:
|
||||
while difficulty_object.hitobject.time > interval:
|
||||
highest_strains.append(max_strain)
|
||||
|
||||
if last is None:
|
||||
max_strain = 0
|
||||
else:
|
||||
decay = (constants.DECAY_BASE ** ((interval - last.hitobject.time) / 1000))
|
||||
max_strain = last.strain * decay
|
||||
|
||||
interval += strain_step
|
||||
|
||||
if difficulty_object.strain > max_strain:
|
||||
max_strain = difficulty_object.strain
|
||||
|
||||
last = difficulty_object
|
||||
|
||||
cdef float difficulty = 0
|
||||
cdef float weight = 1
|
||||
|
||||
#Sort from high to low strain
|
||||
highest_strains.sort(key=int, reverse=True)
|
||||
|
||||
cdef float strain
|
||||
for strain in highest_strains:
|
||||
difficulty += weight * strain
|
||||
weight *= constants.DECAY_WEIGHT
|
||||
|
||||
return difficulty
|
0
pp/catch_the_pp/osu_parser/__init__.py
Normal file
0
pp/catch_the_pp/osu_parser/__init__.py
Normal file
222
pp/catch_the_pp/osu_parser/beatmap.pyx
Normal file
222
pp/catch_the_pp/osu_parser/beatmap.pyx
Normal file
@@ -0,0 +1,222 @@
|
||||
from . import mathhelper
|
||||
from .hitobject import HitObject
|
||||
|
||||
cdef class Beatmap(object):
|
||||
"""
|
||||
Beatmap object for beatmap parsing and handling
|
||||
"""
|
||||
|
||||
cdef public str file_name
|
||||
cdef public int version
|
||||
cdef public int header
|
||||
cdef public dict difficulty
|
||||
cdef public dict timing_points
|
||||
cdef public float slider_point_distance
|
||||
cdef public list hitobjects
|
||||
cdef public int max_combo
|
||||
|
||||
def __init__(self, file_name):
|
||||
"""
|
||||
file_name -- Directory for beatmap file (.osu)
|
||||
"""
|
||||
self.file_name = file_name
|
||||
self.version = -1 #Unknown by default
|
||||
self.header = -1
|
||||
self.difficulty = {}
|
||||
self.timing_points = {
|
||||
"raw_bpm": {}, #Raw bpm modifier code
|
||||
"raw_spm": {}, #Raw speed modifier code
|
||||
"bpm": {}, #Beats pr minute
|
||||
"spm": {} #Speed modifier
|
||||
}
|
||||
self.slider_point_distance = 1 #Changes after [Difficulty] is fully parsed
|
||||
self.hitobjects = []
|
||||
self.max_combo = 0
|
||||
self.parse_beatmap()
|
||||
|
||||
if "ApproachRate" not in self.difficulty.keys(): #Fix old osu version
|
||||
self.difficulty["ApproachRate"] = self.difficulty["OverallDifficulty"]
|
||||
|
||||
cpdef parse_beatmap(self):
|
||||
"""
|
||||
Parses beatmap file line by line by passing each line into parse_line.
|
||||
"""
|
||||
cdef str line
|
||||
with open(self.file_name, encoding="utf8") as file_stream:
|
||||
ver_line = ""
|
||||
while len(ver_line) < 2: #Find the line where beatmap version is spesified (normaly first line)
|
||||
ver_line = file_stream.readline()
|
||||
self.version = int(''.join(list(filter(str.isdigit, ver_line)))) #Set version
|
||||
for line in file_stream:
|
||||
self.parse_line(line.replace("\n", ""))
|
||||
|
||||
cpdef parse_line(self, str line):
|
||||
"""
|
||||
Parse a beatmapfile line.
|
||||
|
||||
Handles lines that are required for our use case (Difficulty, TimingPoints & hitobjects),
|
||||
everything else is skipped.
|
||||
"""
|
||||
if len(line) < 1:
|
||||
return
|
||||
|
||||
if line.startswith("["):
|
||||
if line == "[Difficulty]":
|
||||
self.header = 0
|
||||
elif line == "[TimingPoints]":
|
||||
self.header = 1
|
||||
elif line == "[HitObjects]":
|
||||
self.header = 2
|
||||
self.slider_point_distance = (100 * self.difficulty["SliderMultiplier"]) / self.difficulty["SliderTickRate"]
|
||||
else:
|
||||
self.header = -1
|
||||
return
|
||||
|
||||
if self.header == -1: #We return if we are reading under a header we dont care about
|
||||
return
|
||||
|
||||
if self.header == 0:
|
||||
self.handle_difficulty_propperty(line)
|
||||
elif self.header == 1:
|
||||
self.handle_timing_point(line)
|
||||
elif self.header == 2:
|
||||
self.handle_hitobject(line)
|
||||
|
||||
cpdef handle_difficulty_propperty(self, str propperty):
|
||||
"""
|
||||
Puts the [Difficulty] propperty into the difficulty dict.
|
||||
"""
|
||||
prop = propperty.split(":")
|
||||
self.difficulty[prop[0]] = float(prop[1])
|
||||
|
||||
cpdef handle_timing_point(self, str timing_point):
|
||||
"""
|
||||
Formats timing points used for slider velocity changes,
|
||||
and store them into self.timing_points dict.
|
||||
"""
|
||||
timing_point_split = timing_point.split(",")
|
||||
timing_point_time = int(float(timing_point_split[0])) #Fixes some special mappers special needs
|
||||
timing_point_focus = timing_point_split[1]
|
||||
|
||||
timing_point_type = 1
|
||||
if len(timing_point_split) >= 7: #Fix for old beatmaps that only stores bpm change and timestamp (only BPM change) [v3?]
|
||||
timing_point_type = int(timing_point_split[6])
|
||||
|
||||
if timing_point_type == 0 and not timing_point_focus.startswith("-"):
|
||||
timing_point_focus = "-100"
|
||||
|
||||
if timing_point_focus.startswith("-"): #If not then its not a slider velocity modifier
|
||||
self.timing_points["spm"][timing_point_time] = -100 / float(timing_point_focus) #Convert to normalized value and store
|
||||
self.timing_points["raw_spm"][timing_point_time] = float(timing_point_focus)
|
||||
else:
|
||||
if len(self.timing_points["bpm"]) == 0: #Fixes if hitobjects shows up before bpm is set
|
||||
timing_point_time = 0
|
||||
|
||||
self.timing_points["bpm"][timing_point_time] = 60000 / float(timing_point_focus)#^
|
||||
self.timing_points["raw_bpm"][timing_point_time] = float(timing_point_focus)
|
||||
#This trash of a game resets the spm when bpm change >.>
|
||||
self.timing_points["spm"][timing_point_time] = 1
|
||||
self.timing_points["raw_spm"][timing_point_time] = -100
|
||||
|
||||
cpdef handle_hitobject(self, str line):
|
||||
"""
|
||||
Puts every hitobject into the hitobjects array.
|
||||
|
||||
Creates hitobjects, hitobject_sliders or skip depending on the given data.
|
||||
We skip everything that is not important for us for our use case (Spinners)
|
||||
"""
|
||||
split_object = line.split(",")
|
||||
time = int(split_object[2])
|
||||
object_type = int(split_object[3])
|
||||
|
||||
if not (1 & object_type > 0 or 2 & object_type > 0): #We only want sliders and circles as spinners are random bannanas etc.
|
||||
return
|
||||
|
||||
if 2 & object_type: #Slider
|
||||
repeat = int(split_object[6])
|
||||
pixel_length = float(split_object[7])
|
||||
|
||||
time_point = self.get_timing_point_all(time)
|
||||
|
||||
tick_distance = (100 * self.difficulty["SliderMultiplier"]) / self.difficulty["SliderTickRate"]
|
||||
if self.version >= 8:
|
||||
tick_distance /= (mathhelper.clamp(-time_point["raw_spm"], 10, 1000) / 100)
|
||||
|
||||
curve_split = split_object[5].split("|")
|
||||
curve_points = []
|
||||
for i in range(1, len(curve_split)):
|
||||
vector_split = curve_split[i].split(":")
|
||||
vector = mathhelper.Vec2(int(vector_split[0]), int(vector_split[1]))
|
||||
curve_points.append(vector)
|
||||
|
||||
slider_type = curve_split[0]
|
||||
if self.version <= 6 and len(curve_points) >= 2:
|
||||
if slider_type == "L":
|
||||
slider_type = "B"
|
||||
|
||||
if len(curve_points) == 2:
|
||||
if (int(split_object[0]) == curve_points[0].x and int(split_object[1]) == curve_points[0].y) or (curve_points[0].x == curve_points[1].x and curve_points[0].y == curve_points[1].y):
|
||||
del curve_points[0]
|
||||
slider_type = "L"
|
||||
|
||||
if len(curve_points) == 0: #Incase of ExGon meme (Sliders that acts like hitcircles)
|
||||
hitobject = HitObject(int(split_object[0]), int(split_object[1]), time, 1)
|
||||
else:
|
||||
hitobject = HitObject(int(split_object[0]), int(split_object[1]), time, object_type, slider_type, curve_points, repeat, pixel_length, time_point, self.difficulty, tick_distance)
|
||||
else:
|
||||
hitobject = HitObject(int(split_object[0]), int(split_object[1]), time, object_type)
|
||||
|
||||
self.hitobjects.append(hitobject)
|
||||
self.max_combo += hitobject.get_combo()
|
||||
|
||||
def get_timing_point_all(self, time):
|
||||
"""
|
||||
Returns a object of all current timing types
|
||||
|
||||
time -- timestamp
|
||||
return -- {"raw_bpm": Float, "raw_spm": Float, "bpm": Float, "spm": Float}
|
||||
"""
|
||||
types = {
|
||||
"raw_bpm": 600,
|
||||
"raw_spm": -100,
|
||||
"bpm": 100,
|
||||
"spm": 1
|
||||
} #Will return the default value if timing point were not found
|
||||
for t in types.keys():
|
||||
r = self.get_timing_point(time, t)
|
||||
if r is not None:
|
||||
types[t] = r
|
||||
#else:
|
||||
#print("{} were not found for timestamp {}, using {} instead.".format(t, time, types[t]))
|
||||
|
||||
return types
|
||||
|
||||
def get_timing_point(self, time, timing_type):
|
||||
"""
|
||||
Returns latest timing point by timestamp (Current)
|
||||
|
||||
time -- timestamp
|
||||
timing_type -- mpb, bmp or spm
|
||||
return -- self.timing_points object
|
||||
"""
|
||||
r = None
|
||||
try:
|
||||
for key in sorted(self.timing_points[timing_type].keys(), key=lambda k: k):
|
||||
if key <= time:
|
||||
r = self.timing_points[timing_type][key]
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return r
|
||||
|
||||
def get_object_count(self):
|
||||
"""
|
||||
Get the total hitobject count for the parsed beatmap (Normal hitobjects, sliders & sliderticks)
|
||||
|
||||
return -- total hitobjects for parsed beatmap
|
||||
"""
|
||||
cdef int count = 0
|
||||
for hitobject in self.hitobjects:
|
||||
count += hitobject.get_points()
|
||||
return count
|
166
pp/catch_the_pp/osu_parser/curves.pyx
Normal file
166
pp/catch_the_pp/osu_parser/curves.pyx
Normal file
@@ -0,0 +1,166 @@
|
||||
import math
|
||||
from .. import constants
|
||||
from . import mathhelper
|
||||
|
||||
class Linear(object): #Because it made sense at the time...
|
||||
def __init__(self, points):
|
||||
self.pos = points
|
||||
|
||||
cdef class Bezier(object):
|
||||
cdef public list points, pos
|
||||
cdef public int order
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.order = len(self.points)
|
||||
self.pos = []
|
||||
self.calc_points()
|
||||
|
||||
cpdef calc_points(self):
|
||||
if len(self.pos) != 0: #This should never happen but since im working on this I want to warn myself if I fuck up
|
||||
raise Exception("Bezier was calculated twice!")
|
||||
|
||||
cdef list sub_points = []
|
||||
for i in range(len(self.points)):
|
||||
if i == len(self.points) - 1:
|
||||
sub_points.append(self.points[i])
|
||||
self.bezier(sub_points)
|
||||
sub_points.clear()
|
||||
elif len(sub_points) > 1 and self.points[i] == sub_points[-1]:
|
||||
self.bezier(sub_points)
|
||||
sub_points.clear()
|
||||
|
||||
sub_points.append(self.points[i])
|
||||
|
||||
cpdef bezier(self, list points):
|
||||
cdef int order = len(points)
|
||||
cdef float step = 0.25 / constants.SLIDER_QUALITY / order #Normaly 0.0025
|
||||
cdef float i = 0
|
||||
cdef int n = order - 1
|
||||
|
||||
cdef float x, y
|
||||
cdef int p
|
||||
|
||||
while i < 1 + step:
|
||||
x = 0
|
||||
y = 0
|
||||
|
||||
for p in range(n + 1):
|
||||
a = mathhelper.cpn(p, n) * ((1 - i) ** (n - p)) * (i ** p)
|
||||
x += a * points[p].x
|
||||
y += a * points[p].y
|
||||
|
||||
point = mathhelper.Vec2(x, y)
|
||||
self.pos.append(point)
|
||||
i += step
|
||||
|
||||
def point_at_distance(self, length):
|
||||
return {
|
||||
0: False,
|
||||
1: self.points[0],
|
||||
}.get(self.order, self.rec(length))
|
||||
|
||||
def rec(self, length):
|
||||
return mathhelper.point_at_distance(self.pos, length)
|
||||
|
||||
cdef class Catmull(object): #Yes... I cry deep down on the inside aswell
|
||||
cdef public list points, pos
|
||||
cdef public int order
|
||||
cdef public float step
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.order = len(points)
|
||||
self.step = 2.5 / constants.SLIDER_QUALITY #Normaly 0.025
|
||||
self.pos = []
|
||||
self.calc_points()
|
||||
|
||||
cpdef calc_points(self):
|
||||
if len(self.pos) != 0: #This should never happen but since im working on this I want to warn myself if I fuck up
|
||||
raise Exception("Catmull was calculated twice!")
|
||||
|
||||
cdef int x
|
||||
cdef float t
|
||||
cdef object v1, v2, v3
|
||||
for x in range(self.order - 1):
|
||||
t = 0
|
||||
while t < self.step + 1:
|
||||
if x >= 1:
|
||||
v1 = self.points[x - 1]
|
||||
else:
|
||||
v1 = self.points[x]
|
||||
|
||||
v2 = self.points[x]
|
||||
|
||||
if x + 1 < self.order:
|
||||
v3 = self.points[x + 1]
|
||||
else:
|
||||
v3 = v2.calc(1, v2.calc(-1, v1))
|
||||
|
||||
if x + 2 < self.order:
|
||||
v4 = self.points[x + 2]
|
||||
else:
|
||||
v4 = v3.calc(1, v3.calc(-1, v2))
|
||||
|
||||
point = get_point([v1, v2, v3, v4], t)
|
||||
self.pos.append(point)
|
||||
t += self.step
|
||||
|
||||
def point_at_distance(self, length):
|
||||
return {
|
||||
0: False,
|
||||
1: self.points[0],
|
||||
}.get(self.order, self.rec(length))
|
||||
|
||||
def rec(self, length):
|
||||
return mathhelper.point_at_distance(self.pos, length)
|
||||
|
||||
cdef class Perfect(object):
|
||||
cdef public list points
|
||||
cdef float cx, cy
|
||||
cdef float radius
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.cx = 0
|
||||
self.cy = 0
|
||||
self.radius = 0
|
||||
self.setup_path()
|
||||
|
||||
def setup_path(self):
|
||||
self.cx, self.cy, self.radius = get_circum_circle(self.points)
|
||||
if is_left(self.points):
|
||||
self.radius *= -1
|
||||
|
||||
cpdef point_at_distance(self, float length):
|
||||
cdef float radians = length / self.radius
|
||||
return rotate(self.cx, self.cy, self.points[0], radians)
|
||||
|
||||
cpdef object get_point(object p, float length):
|
||||
cdef float x = mathhelper.catmull([o.x for o in p], length)
|
||||
cdef float y = mathhelper.catmull([o.y for o in p], length)
|
||||
return mathhelper.Vec2(x, y)
|
||||
|
||||
cpdef tuple get_circum_circle(list p):
|
||||
cdef float d = 2 * (p[0].x * (p[1].y - p[2].y) + p[1].x * (p[2].y - p[0].y) + p[2].x * (p[0].y - p[1].y))
|
||||
|
||||
if d == 0:
|
||||
raise Exception("Invalid circle! Unable to chose angle.")
|
||||
|
||||
cdef float ux = ((pow(p[0].x, 2) + pow(p[0].y, 2)) * (p[1].y - p[2].y) + (pow(p[1].x, 2) + pow(p[1].y, 2)) * (p[2].y - p[0].y) + (pow(p[2].x, 2) + pow(p[2].y, 2)) * (p[0].y - p[1].y)) / d
|
||||
cdef float uy = ((pow(p[0].x, 2) + pow(p[0].y, 2)) * (p[2].x - p[1].x) + (pow(p[1].x, 2) + pow(p[1].y, 2)) * (p[0].x - p[2].x) + (pow(p[2].x, 2) + pow(p[2].y, 2)) * (p[1].x - p[0].x)) / d
|
||||
|
||||
cdef float px = ux - p[0].x
|
||||
cdef float py = uy - p[0].y
|
||||
cdef float r = pow(pow(px, 2) + pow(py, 2), 0.5)
|
||||
|
||||
return ux, uy, r
|
||||
|
||||
cpdef float is_left(object p):
|
||||
return ((p[1].x - p[0].x) * (p[2].y - p[0].y) - (p[1].y - p[0].y) * (p[2].x - p[0].x)) < 0
|
||||
|
||||
cpdef object rotate(float cx, float cy, object p, float radians):
|
||||
cdef float cos = math.cos(radians)
|
||||
cdef float sin = math.sin(radians)
|
||||
|
||||
return mathhelper.Vec2((cos * (p.x - cx)) - (sin * (p.y - cy)) + cx, (sin * (p.x - cx)) + (cos * (p.y - cy)) + cy)
|
169
pp/catch_the_pp/osu_parser/hitobject.pyx
Normal file
169
pp/catch_the_pp/osu_parser/hitobject.pyx
Normal file
@@ -0,0 +1,169 @@
|
||||
import copy
|
||||
from . import mathhelper
|
||||
from . import curves
|
||||
|
||||
cdef class SliderTick:
|
||||
cdef public float x, y, time
|
||||
|
||||
def __init__(self, x, y, time):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.time = time
|
||||
|
||||
cdef class HitObject(object):
|
||||
cdef public float x, y, time, end_time, pixel_length, tick_distance, duration
|
||||
cdef public int type, repeat
|
||||
cdef public str slider_type
|
||||
cdef public list curve_points, ticks, end_ticks, path
|
||||
cdef public dict timing_point
|
||||
cdef public object difficulty, end
|
||||
|
||||
def __init__(self, x, y, time, object_type, slider_type = None, curve_points = None, repeat = 1, pixel_length = 0, timing_point = None, difficulty = None, tick_distance = 1):
|
||||
"""
|
||||
HitObject params for normal hitobject and sliders
|
||||
|
||||
x -- x position
|
||||
y -- y position
|
||||
time -- timestamp
|
||||
object_type -- type of object (bitmask)
|
||||
|
||||
[+] IF SLIDER
|
||||
slider_type -- type of slider (L, P, B, C)
|
||||
curve_points -- points in the curve path
|
||||
repeat -- amount of repeats for the slider (+1)
|
||||
pixel_length -- length of the slider
|
||||
timing_point -- ref of current timing point for the timestamp
|
||||
difficulty -- ref of beatmap difficulty
|
||||
tick_distance -- distance betwin each slidertick
|
||||
"""
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.time = time
|
||||
self.end_time = 0
|
||||
self.type = object_type
|
||||
|
||||
#isSlider?
|
||||
if 2 & self.type:
|
||||
self.slider_type = slider_type
|
||||
self.curve_points = [mathhelper.Vec2(self.x, self.y)] + curve_points
|
||||
self.repeat = repeat
|
||||
self.pixel_length = pixel_length
|
||||
|
||||
#For slider tick calculations
|
||||
self.timing_point = timing_point
|
||||
self.difficulty = difficulty
|
||||
self.tick_distance = tick_distance
|
||||
self.duration = (int(self.timing_point["raw_bpm"]) * (pixel_length / (self.difficulty["SliderMultiplier"] * self.timing_point["spm"])) / 100) * self.repeat
|
||||
|
||||
self.ticks = []
|
||||
self.end_ticks = []
|
||||
self.path = []
|
||||
self.end = None
|
||||
|
||||
self.calc_slider()
|
||||
|
||||
def calc_slider(self, calc_path = False):
|
||||
#Fix broken objects
|
||||
if self.slider_type == "P" and len(self.curve_points) > 3:
|
||||
self.slider_type = "B"
|
||||
elif len(self.curve_points) == 2:
|
||||
self.slider_type = "L"
|
||||
|
||||
#Make curve
|
||||
if self.slider_type == "P": #Perfect
|
||||
try:
|
||||
curve = curves.Perfect(self.curve_points)
|
||||
except:
|
||||
curve = curves.Bezier(self.curve_points)
|
||||
self.slider_type = "B"
|
||||
elif self.slider_type == "B": #Bezier
|
||||
curve = curves.Bezier(self.curve_points)
|
||||
elif self.slider_type == "C": #Catmull
|
||||
curve = curves.Catmull(self.curve_points)
|
||||
|
||||
#Quickest to skip this
|
||||
if calc_path: #Make path if requested (For drawing visual for testing)
|
||||
if self.slider_type == "L": #Linear
|
||||
self.path = curves.Linear(self.curve_points).pos
|
||||
elif self.slider_type == "P": #Perfect
|
||||
self.path = []
|
||||
l = 0
|
||||
step = 5
|
||||
while l <= self.pixel_length:
|
||||
self.path.append(curve.point_at_distance(l))
|
||||
l += step
|
||||
elif self.slider_type == "B": #Bezier
|
||||
self.path = curve.pos
|
||||
elif self.slider_type == "C": #Catmull
|
||||
self.path = curve.pos
|
||||
else:
|
||||
raise Exception("Slidertype not supported! ({})".format(self.slider_type))
|
||||
|
||||
#Set slider ticks
|
||||
current_distance = self.tick_distance
|
||||
time_add = self.duration * (self.tick_distance / (self.pixel_length * self.repeat))
|
||||
|
||||
while current_distance < self.pixel_length - self.tick_distance / 8:
|
||||
if self.slider_type == "L": #Linear
|
||||
point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], current_distance)
|
||||
else: #Perfect, Bezier & Catmull uses the same function
|
||||
point = curve.point_at_distance(current_distance)
|
||||
|
||||
self.ticks.append(SliderTick(point.x, point.y, self.time + time_add * (len(self.ticks) + 1)))
|
||||
current_distance += self.tick_distance
|
||||
|
||||
#Adds slider_ends / repeat_points
|
||||
repeat_id = 1
|
||||
repeat_bonus_ticks = []
|
||||
while repeat_id < self.repeat:
|
||||
dist = (1 & repeat_id) * self.pixel_length
|
||||
time_offset = (self.duration / self.repeat) * repeat_id
|
||||
|
||||
if self.slider_type == "L": #Linear
|
||||
point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], dist)
|
||||
else: #Perfect, Bezier & Catmull uses the same function
|
||||
point = curve.point_at_distance(dist)
|
||||
|
||||
self.end_ticks.append(SliderTick(point.x, point.y, self.time + time_offset))
|
||||
|
||||
#Adds the ticks that already exists on the slider back (but reversed)
|
||||
repeat_ticks = copy.deepcopy(self.ticks)
|
||||
|
||||
if 1 & repeat_id: #We have to reverse the timing normalizer
|
||||
repeat_ticks = list(reversed(repeat_ticks))
|
||||
normalize_time_value = self.time + (self.duration / self.repeat)
|
||||
else:
|
||||
normalize_time_value = self.time
|
||||
|
||||
#Correct timing
|
||||
for tick in repeat_ticks:
|
||||
tick.time = self.time + time_offset + abs(tick.time - normalize_time_value)
|
||||
|
||||
repeat_bonus_ticks += repeat_ticks
|
||||
|
||||
repeat_id += 1
|
||||
|
||||
self.ticks += repeat_bonus_ticks
|
||||
|
||||
#Add endpoint for slider
|
||||
dist_end = (1 & self.repeat) * self.pixel_length
|
||||
if self.slider_type == "L": #Linear
|
||||
point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], dist_end)
|
||||
else: #Perfect, Bezier & Catmull uses the same function
|
||||
point = curve.point_at_distance(dist_end)
|
||||
|
||||
self.end_ticks.append(SliderTick(point.x, point.y, self.time + self.duration))
|
||||
|
||||
def get_combo(self):
|
||||
"""
|
||||
Returns the combo given by this object
|
||||
1 if normal hitobject, 2+ if slider (adds sliderticks)
|
||||
"""
|
||||
if 2 & self.type: #Slider
|
||||
val = 1 #Start of the slider
|
||||
val += len(self.ticks) #The amount of sliderticks
|
||||
val += self.repeat #Reverse slider
|
||||
else: #Normal
|
||||
val = 1 #Itself...
|
||||
|
||||
return val
|
124
pp/catch_the_pp/osu_parser/mathhelper.pyx
Normal file
124
pp/catch_the_pp/osu_parser/mathhelper.pyx
Normal file
@@ -0,0 +1,124 @@
|
||||
import math
|
||||
|
||||
cpdef float clamp(float value, float mn, float mx):
|
||||
return min(max(mn, value), mx)
|
||||
|
||||
cpdef sign(float value):
|
||||
if value == 0:
|
||||
return 0
|
||||
elif value > 0:
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
cpdef cpn(int p, int n):
|
||||
if p < 0 or p > n:
|
||||
return 0
|
||||
p = min(p, n - p)
|
||||
out = 1
|
||||
for i in range(1, p + 1):
|
||||
out = out * (n - p + i) / i
|
||||
|
||||
return out
|
||||
|
||||
cpdef float catmull(p, t): # WARNING: Worst math formula incomming
|
||||
return 0.5 * (
|
||||
(2 * p[1]) +
|
||||
(-p[0] + p[2]) * t +
|
||||
(2 * p[0] - 5 * p[1] + 4 * p[2] - p[3]) * (t ** 2) +
|
||||
(-p[0] + 3 * p[1] - 3 * p[2] + p[3]) * (t ** 3))
|
||||
|
||||
cpdef Vec2 point_on_line(Vec2 p0, Vec2 p1, float length):
|
||||
cdef float full_length = (((p1.x - p0.x) ** 2) + ((p1.y - p0.y) ** 2)) ** 0.5
|
||||
cdef float n = full_length - length
|
||||
|
||||
if full_length == 0: #Fix for something that seems unknown... (We warn if this happens)
|
||||
full_length = 1
|
||||
|
||||
cdef float x = (n * p0.x + length * p1.x) / full_length
|
||||
cdef float y = (n * p0.y + length * p1.y) / full_length
|
||||
|
||||
return Vec2(x, y)
|
||||
|
||||
cpdef float angle_from_points(Vec2 p0, Vec2 p1):
|
||||
return math.atan2(p1.y - p0.y, p1.x - p0.x)
|
||||
|
||||
cpdef float distance_from_points(array):
|
||||
cdef float distance = 0
|
||||
cdef int i
|
||||
|
||||
for i in range(1, len(array)):
|
||||
distance += array[i].distance(array[i - 1])
|
||||
|
||||
return distance
|
||||
|
||||
cpdef Vec2 cart_from_pol(r, t):
|
||||
cdef float x = (r * math.cos(t))
|
||||
cdef float y = (r * math.sin(t))
|
||||
|
||||
return Vec2(x, y)
|
||||
|
||||
cpdef point_at_distance(array, float distance): #TODO: Optimize...
|
||||
cdef int i = 0
|
||||
cdef float x, y, current_distance = 0, new_distance = 0, angle
|
||||
cdef Vec2 coord, cart
|
||||
|
||||
if len(array) < 2:
|
||||
return Vec2(0, 0)
|
||||
|
||||
if distance == 0:
|
||||
return array[0]
|
||||
|
||||
if distance_from_points(array) <= distance:
|
||||
return array[len(array) - 1]
|
||||
|
||||
for i in range(len(array) - 2):
|
||||
x = (array[i].x - array[i + 1].x)
|
||||
y = (array[i].y - array[i + 1].y)
|
||||
|
||||
new_distance = math.sqrt(x * x + y * y)
|
||||
current_distance += new_distance
|
||||
|
||||
if distance <= current_distance:
|
||||
break
|
||||
|
||||
current_distance -= new_distance
|
||||
|
||||
if distance == current_distance:
|
||||
return array[i]
|
||||
else:
|
||||
angle = angle_from_points(array[i], array[i + 1])
|
||||
cart = cart_from_pol((distance - current_distance), angle)
|
||||
|
||||
if array[i].x > array[i + 1].x:
|
||||
coord = Vec2((array[i].x - cart.x), (array[i].y - cart.y))
|
||||
else:
|
||||
coord = Vec2((array[i].x + cart.y), (array[i].y + cart.y))
|
||||
return coord
|
||||
|
||||
cdef class Vec2(object):
|
||||
cdef public float x
|
||||
cdef public float y
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __richcmp__(x, y, op):
|
||||
if op == 2:#Py_EQ
|
||||
return x.__is_equal(y)
|
||||
else:#Py_NE
|
||||
return not x.__is_equal(y)
|
||||
|
||||
def __is_equal(self, other):
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
cpdef float distance(Vec2 self, Vec2 other):
|
||||
cdef float x = self.x - other.x
|
||||
cdef float y = self.y - other.y
|
||||
return (x*x + y*y) ** 0.5 #sqrt, lol
|
||||
|
||||
cpdef Vec2 calc(Vec2 self, float value, Vec2 other): #I dont know what to call this function yet
|
||||
cdef float x = self.x + value * other.x
|
||||
cdef float y = self.y + value * other.y
|
||||
return Vec2(x, y)
|
41
pp/catch_the_pp/ppCalc.pyx
Normal file
41
pp/catch_the_pp/ppCalc.pyx
Normal file
@@ -0,0 +1,41 @@
|
||||
import math
|
||||
|
||||
cpdef calculate_pp(diff, accuracy, combo, miss):
|
||||
"""
|
||||
Calculate pp for gameplay
|
||||
|
||||
diff -- Difficulty object
|
||||
accuracy -- Accuracy of the play (Float 0-1)
|
||||
combo -- MaxCombo achived during the play (Int)
|
||||
miss -- Amount of misses during the play (Int)
|
||||
return -- Total pp for gameplay
|
||||
"""
|
||||
cdef float pp = (((5 * diff.star_rating / 0.0049) - 4) ** 2) / 100000
|
||||
cdef float length_bonus = 0.95 + 0.4 * min(1, combo / 3000)
|
||||
if combo > 3000:
|
||||
length_bonus += math.log10(combo / 3000) * 0.5
|
||||
|
||||
pp *= length_bonus
|
||||
pp *= (0.97 ** miss)
|
||||
pp *= min((combo ** 0.8) / (diff.beatmap.max_combo ** 0.8), 1)
|
||||
|
||||
if diff.beatmap.difficulty["ApproachRate"] > 9:
|
||||
pp *= 1 + 0.1 * (diff.beatmap.difficulty["ApproachRate"] - 9)
|
||||
if diff.beatmap.difficulty["ApproachRate"] < 8:
|
||||
pp *= 1 + 0.025 * (8 - diff.beatmap.difficulty["ApproachRate"])
|
||||
|
||||
if diff.mods & 1 << 3 > 0: #HD
|
||||
pp *= 1.05 + 0.075 * (10 - min(10, diff.beatmap.difficulty["ApproachRate"]))
|
||||
|
||||
if diff.mods & 1 << 10 > 0: #FL
|
||||
pp *= 1.35 * length_bonus
|
||||
|
||||
pp *= (accuracy ** 5.5)
|
||||
|
||||
if diff.mods & 1 << 0 > 0: #NF
|
||||
pp *= 0.9
|
||||
|
||||
if diff.mods & 1 << 12 > 0: #SO
|
||||
pp *= 0.95
|
||||
|
||||
return pp
|
899
pp/catch_the_pp/reanimate.osu
Normal file
899
pp/catch_the_pp/reanimate.osu
Normal file
@@ -0,0 +1,899 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: REANIMATE.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 57263
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.5
|
||||
Mode: 2
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 0
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 13470,24504,35539,41056,46573,50711,57608,68642,79677,90711,101746,112780,118298,119677,129246,140367,145884
|
||||
DistanceSpacing: 0.6
|
||||
BeatDivisor: 8
|
||||
GridSize: 8
|
||||
TimelineZoom: 2.372001
|
||||
|
||||
[Metadata]
|
||||
Title:REANIMATE
|
||||
TitleUnicode:REANIMATE
|
||||
Artist:Warak
|
||||
ArtistUnicode:Warak
|
||||
Creator:- Magic Bomb -
|
||||
Version:Imagination
|
||||
Source:节奏大师
|
||||
Tags:MBomb rhythm master symphonic drumstep JBHyperion Zirox JBH
|
||||
BeatmapID:1042702
|
||||
BeatmapSetID:489190
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4
|
||||
OverallDifficulty:9.4
|
||||
ApproachRate:9.4
|
||||
SliderMultiplier:2.1
|
||||
SliderTickRate:2
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"REVIVE.png",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
2436,344.827586206897,4,2,77,100,1,0
|
||||
13470,-100,4,2,80,100,0,0
|
||||
13556,-100,4,2,78,85,0,0
|
||||
24160,-100,4,2,77,80,0,0
|
||||
24504,-100,4,2,78,80,0,0
|
||||
24677,-100,4,2,77,80,0,0
|
||||
27263,-100,4,2,78,80,0,0
|
||||
27436,-100,4,2,77,80,0,0
|
||||
30022,-100,4,2,78,80,0,0
|
||||
30194,-100,4,2,77,80,0,0
|
||||
32780,-100,4,2,78,80,0,0
|
||||
32953,-100,4,2,77,80,0,0
|
||||
35539,-100,4,2,78,90,0,0
|
||||
35711,-100,4,2,77,90,0,0
|
||||
38298,-100,4,2,78,90,0,0
|
||||
38470,-100,4,2,77,90,0,0
|
||||
41056,-100,4,2,78,90,0,0
|
||||
41229,-100,4,2,77,90,0,0
|
||||
43815,-100,4,2,78,90,0,0
|
||||
45194,-100,4,2,77,90,0,0
|
||||
46573,-100,4,2,78,90,0,1
|
||||
46746,-100,4,2,78,90,0,1
|
||||
48642,-66.6666666666667,4,2,78,90,0,1
|
||||
48987,-100,4,2,78,90,0,1
|
||||
49677,-66.6666666666667,4,2,78,90,0,1
|
||||
49849,-100,4,2,78,90,0,1
|
||||
56229,-66.6666666666667,4,2,78,90,0,1
|
||||
57263,-66.6666666666667,4,2,78,80,0,0
|
||||
57608,-100,4,2,78,90,0,1
|
||||
57780,-100,4,2,78,90,0,1
|
||||
63125,-100,4,2,78,90,0,1
|
||||
63470,-66.6666666666667,4,2,78,90,0,1
|
||||
63815,-100,4,2,78,90,0,1
|
||||
64332,-66.6666666666667,4,2,78,90,0,1
|
||||
64504,-100,4,2,78,90,0,1
|
||||
65539,-66.6666666666667,4,2,78,90,0,1
|
||||
65711,-100,4,2,78,90,0,1
|
||||
65884,-66.6666666666667,4,2,78,90,0,1
|
||||
66573,-100,4,2,78,90,0,1
|
||||
66918,-83.3333333333333,4,2,78,90,0,1
|
||||
67263,-100,4,2,77,80,0,1
|
||||
68642,-100,4,2,78,80,0,0
|
||||
78298,-100,4,2,77,80,0,0
|
||||
79677,-100,4,2,79,70,0,0
|
||||
90711,-100,4,2,78,70,0,0
|
||||
99073,-100,4,2,77,90,0,0
|
||||
100367,-100,4,2,77,80,0,0
|
||||
101056,-100,4,2,77,70,0,0
|
||||
101746,-100,4,2,77,80,0,0
|
||||
111573,-100,4,2,77,50,0,0
|
||||
113125,-100,4,2,77,80,0,0
|
||||
118298,-66.6666666666667,4,2,78,90,0,1
|
||||
118642,-100,4,2,78,90,0,1
|
||||
120711,-83.3333333333333,4,2,78,90,0,1
|
||||
121056,-100,4,2,78,90,0,1
|
||||
124849,-66.6666666666667,4,2,78,90,0,1
|
||||
125022,-100,4,2,78,90,0,1
|
||||
125539,-66.6666666666667,4,2,78,90,0,1
|
||||
125711,-100,4,2,78,90,0,1
|
||||
125884,-66.6666666666667,4,2,78,90,0,1
|
||||
126056,-100,4,2,78,90,0,1
|
||||
126229,-66.6666666666667,4,2,78,90,0,1
|
||||
126401,-100,4,2,78,90,0,1
|
||||
128987,-100,4,2,78,90,0,0
|
||||
129332,-100,4,2,78,90,0,1
|
||||
130194,-66.6666666666667,4,2,78,90,0,1
|
||||
130367,-100,4,2,78,90,0,1
|
||||
130884,-66.6666666666667,4,2,78,90,0,1
|
||||
131056,-100,4,2,78,90,0,1
|
||||
133470,-66.6666666666667,4,2,78,90,0,1
|
||||
133815,-100,4,2,78,90,0,1
|
||||
134158,-66.6666666666667,4,2,78,90,0,1
|
||||
134418,-100,4,2,78,90,0,1
|
||||
138987,-66.6666666666667,4,2,77,100,0,1
|
||||
139677,-83.3333333333333,4,2,77,100,0,1
|
||||
140022,-100,4,2,77,100,0,1
|
||||
140367,-100,4,2,79,80,0,0
|
||||
144504,-100,4,2,77,80,0,0
|
||||
145194,-100,4,2,77,100,0,1
|
||||
145280,-100,4,2,77,40,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 183,47,49
|
||||
Combo2 : 223,170,70
|
||||
Combo3 : 84,114,98
|
||||
Combo4 : 92,107,131
|
||||
|
||||
[HitObjects]
|
||||
192,192,2436,5,4,0:0:0:0:
|
||||
141,200,2522,1,0,0:0:0:0:
|
||||
102,168,2608,1,0,0:0:0:0:
|
||||
101,117,2694,1,0,0:0:0:0:
|
||||
256,192,2780,1,0,0:0:0:0:
|
||||
144,192,2953,1,0,0:0:0:0:
|
||||
392,192,3125,2,0,P|504:112|488:64,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
432,48,3556,1,0,0:0:0:0:
|
||||
376,56,3642,1,0,0:0:0:0:
|
||||
344,95,3728,1,0,0:0:0:0:
|
||||
496,192,3815,6,0,L|400:152,2,105,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
16,192,4504,2,0,L|112:152,2,105,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
448,192,5194,5,4,0:0:0:0:
|
||||
422,146,5280,1,0,0:0:0:0:
|
||||
379,117,5366,1,0,0:0:0:0:
|
||||
327,110,5452,1,0,0:0:0:0:
|
||||
224,192,5539,1,0,0:0:0:0:
|
||||
496,192,5711,1,0,0:0:0:0:
|
||||
160,192,5884,2,0,P|88:80|120:56,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
392,192,6401,1,0,0:0:0:0:
|
||||
96,192,6573,5,2,0:0:0:0:
|
||||
96,192,6832,1,0,0:0:0:0:
|
||||
256,192,6918,2,0,B|184:120|184:120|128:120,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
416,192,7263,1,2,0:0:0:0:
|
||||
416,192,7522,1,0,0:0:0:0:
|
||||
256,192,7608,2,0,B|328:120|328:120|384:120,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,7953,5,4,0:0:0:0:
|
||||
112,192,8039,1,0,0:0:0:0:
|
||||
112,192,8125,1,0,0:0:0:0:
|
||||
176,192,8211,1,0,0:0:0:0:
|
||||
272,192,8298,1,0,0:0:0:0:
|
||||
16,192,8470,1,0,0:0:0:0:
|
||||
272,192,8642,2,0,L|496:192,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
320,192,9160,1,0,0:0:0:0:
|
||||
482,192,9332,5,2,0:0:0:0:
|
||||
308,192,9504,2,0,L|244:192,1,52.5
|
||||
164,192,9677,2,0,L|220:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
372,192,9849,2,0,L|308:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
116,192,10022,2,0,P|52:88|108:56,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
448,192,10539,1,2,0:0:0:0:
|
||||
256,192,10625,1,0,0:0:0:0:
|
||||
64,192,10711,5,4,0:0:0:0:
|
||||
256,192,10970,1,0,0:0:0:0:
|
||||
448,192,11056,1,2,0:0:0:0:
|
||||
256,192,11315,1,0,0:0:0:0:
|
||||
96,192,11401,1,2,0:0:0:0:
|
||||
256,192,11660,1,0,0:0:0:0:
|
||||
400,192,11746,1,2,0:0:0:0:
|
||||
224,192,12004,1,2,0:0:0:0:
|
||||
224,192,12091,5,0,0:0:0:0:
|
||||
464,192,12263,2,0,L|411:192,1,52.5
|
||||
176,192,12436,2,0,P|96:120|160:88,1,210,2|2,0:0|0:0,0:0:0:0:
|
||||
208,192,12867,2,0,L|272:192,2,52.5
|
||||
144,192,13125,2,0,P|40:128|40:112,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,13470,5,4,0:0:0:0:
|
||||
235,192,13556,1,0,0:0:0:0:
|
||||
182,192,13642,1,0,0:0:0:0:
|
||||
130,192,13728,1,0,0:0:0:0:
|
||||
288,192,13815,2,0,L|392:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
120,192,14160,2,0,P|47:144|96:80,1,157.5,8|0,0:0|0:0,0:0:0:0:
|
||||
256,192,14504,2,0,L|320:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,14677,2,0,L|192:192,1,52.5
|
||||
32,192,14849,5,2,0:0:0:0:
|
||||
176,192,15022,1,2,0:0:0:0:
|
||||
32,192,15194,2,0,L|192:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
480,192,15539,1,8,0:0:0:0:
|
||||
368,192,15712,1,0,0:0:0:0:
|
||||
480,192,15884,2,0,L|320:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
136,192,16229,5,2,0:0:0:0:
|
||||
186,185,16315,1,0,0:0:0:0:
|
||||
215,143,16401,1,0,0:0:0:0:
|
||||
204,93,16487,1,0,0:0:0:0:
|
||||
128,192,16573,1,2,0:0:0:0:
|
||||
384,192,16746,1,0,0:0:0:0:
|
||||
64,192,16918,2,0,P|120:96|216:80,1,210,8|2,0:0|0:0,0:0:0:0:
|
||||
488,192,17436,1,0,0:0:0:0:
|
||||
199,192,17608,6,0,L|272:112,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
184,192,17867,1,0,0:0:0:0:
|
||||
16,192,17953,2,0,L|184:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
320,192,18298,1,8,0:0:0:0:
|
||||
224,192,18557,1,0,0:0:0:0:
|
||||
392,192,18642,2,0,L|224:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
88,192,18987,5,2,0:0:0:0:
|
||||
16,192,19073,1,0,0:0:0:0:
|
||||
16,192,19160,1,0,0:0:0:0:
|
||||
64,192,19246,1,0,0:0:0:0:
|
||||
136,192,19332,1,2,0:0:0:0:
|
||||
408,192,19504,1,0,0:0:0:0:
|
||||
136,192,19677,2,0,P|224:136|272:184,1,157.5,8|0,0:0|0:0,0:0:0:0:
|
||||
64,192,20022,2,0,P|32:128|56:120,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
320,192,20367,5,2,0:0:0:0:
|
||||
448,192,20539,2,0,L|392:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
240,192,20711,2,0,L|184:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
344,192,20884,2,0,L|280:192,1,52.5
|
||||
128,192,21056,2,0,P|48:112|104:64,1,210,8|2,0:0|0:0,0:0:0:0:
|
||||
416,192,21573,2,0,L|456:152,1,52.5
|
||||
256,192,21746,6,0,L|144:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
216,192,22004,1,0,0:0:0:0:
|
||||
368,192,22091,1,0,0:0:0:0:
|
||||
417,179,22177,1,0,0:0:0:0:
|
||||
441,134,22263,1,0,0:0:0:0:
|
||||
425,86,22349,1,0,0:0:0:0:
|
||||
160,192,22436,2,0,P|72:112|208:48,1,315,8|0,0:0|0:0,0:0:0:0:
|
||||
352,192,23039,1,0,0:0:0:0:
|
||||
496,192,23125,5,2,0:0:0:0:
|
||||
400,192,23298,2,0,L|512:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
48,192,23814,1,8,0:0:0:0:
|
||||
176,192,24160,2,0,L|88:136,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,24504,5,2,0:0:0:0:
|
||||
192,192,24849,2,0,L|416:192,1,210,0|2,0:0|0:0,0:0:0:0:
|
||||
64,192,25539,2,0,L|288:192,1,210,0|2,0:0|0:0,0:0:0:0:
|
||||
496,192,26229,2,0,L|272:192,2,210,2|0|2,0:0|0:0|0:0,0:0:0:0:
|
||||
32,192,27263,6,0,L|120:136,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
304,192,27608,2,0,P|384:120|360:88,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,27953,2,0,B|384:168|384:168|272:168,1,315,2|0,0:0|0:0,0:0:0:0:
|
||||
16,192,28642,1,2,0:0:0:0:
|
||||
176,192,28987,2,0,L|392:192,2,210,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
496,192,30022,6,0,L|384:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
112,192,30367,2,0,P|40:104|80:64,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
168,192,30711,2,0,L|328:192,1,157.5
|
||||
168,192,31056,2,0,L|8:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
168,192,31401,2,0,B|464:168|424:72|352:72,1,315,2|0,0:0|0:0,0:0:0:0:
|
||||
72,72,32091,2,0,B|16:70|-23:166|272:190,1,315,2|0,0:0|0:0,0:0:0:0:
|
||||
16,192,32780,5,2,0:0:0:0:
|
||||
256,192,33125,2,0,L|40:192,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,33815,2,0,P|400:112|352:72,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,34160,6,0,L|392:192,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,34677,1,0,0:0:0:0:
|
||||
496,192,34849,2,0,L|280:192,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
16,192,35539,6,0,L|128:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,35884,2,0,L|184:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,36229,2,0,L|384:192,1,105
|
||||
224,192,36573,2,0,L|112:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
384,192,36918,6,0,L|272:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
24,192,37263,2,0,L|240:192,2,210,0|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
176,192,38125,1,0,0:0:0:0:
|
||||
432,192,38298,6,0,L|328:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,38642,2,0,L|384:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
128,192,38987,2,0,L|8:192,1,105
|
||||
192,192,39332,2,0,L|304:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
23,192,39677,6,0,L|135:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
383,192,40022,2,0,L|167:192,2,210,0|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
480,192,40884,1,0,0:0:0:0:
|
||||
176,192,41056,5,2,0:0:0:0:
|
||||
304,192,41229,1,0,0:0:0:0:
|
||||
24,192,41401,2,0,L|128:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
400,192,41746,2,0,L|505:192,1,105
|
||||
129,192,42091,2,0,L|24:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
383,192,42436,6,0,L|488:192,2,105,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
88,192,42953,1,0,0:0:0:0:
|
||||
344,192,43125,2,0,L|239:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,43470,2,0,L|336:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
160,192,43815,6,0,L|48:192,3,105,2|0|2|0,0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
352,192,44504,2,0,L|464:192,3,105,2|0|2|0,0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
192,192,45194,5,2,0:0:0:0:
|
||||
456,192,45539,1,0,0:0:0:0:
|
||||
32,192,45884,2,0,P|104:96|328:120,1,367.5,2|0,0:0|0:0,0:0:0:0:
|
||||
184,192,46573,6,0,L|24:192,1,157.5,6|0,0:0|0:0,0:0:0:0:
|
||||
184,192,46918,1,8,0:0:0:0:
|
||||
64,192,47091,1,0,0:0:0:0:
|
||||
344,192,47263,2,0,L|400:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
192,192,47436,2,0,L|136:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
400,192,47608,1,8,0:0:0:0:
|
||||
128,192,47780,2,0,L|176:168,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,47953,6,0,P|404:184|452:160,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
180,192,48298,1,8,0:0:0:0:
|
||||
452,192,48470,2,0,L|396:192,1,52.5
|
||||
96,192,48642,1,0,0:0:0:0:
|
||||
110,155,48685,1,0,0:0:0:0:
|
||||
137,127,48728,1,0,0:0:0:0:
|
||||
174,113,48771,1,0,0:0:0:0:
|
||||
213,114,48814,1,0,0:0:0:0:
|
||||
248,130,48857,1,0,0:0:0:0:
|
||||
274,159,48900,1,0,0:0:0:0:
|
||||
287,196,48943,1,0,0:0:0:0:
|
||||
128,192,48987,2,0,L|24:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
320,192,49332,6,0,L|496:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
320,192,49677,2,0,L|224:192,1,78.7500030040742,8|0,0:0|0:0,0:0:0:0:
|
||||
80,192,49849,2,0,L|136:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,50022,2,0,L|232:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
64,192,50194,2,0,L|120:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
312,192,50367,2,0,L|272:152,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
120,192,50539,2,0,L|176:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
336,192,50711,6,0,L|224:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,51056,2,0,L|384:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
224,192,51401,2,0,L|352:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
272,192,51660,1,2,0:0:0:0:
|
||||
120,192,51746,2,0,L|8:192,1,105,8|2,0:0|0:0,0:0:0:0:
|
||||
360,192,52091,6,0,L|464:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
192,192,52436,1,8,0:0:0:0:
|
||||
464,192,52608,1,0,0:0:0:0:
|
||||
484,176,52651,1,0,0:0:0:0:
|
||||
497,153,52694,1,0,0:0:0:0:
|
||||
497,127,52737,1,0,0:0:0:0:
|
||||
360,192,52780,1,2,0:0:0:0:
|
||||
200,192,52867,1,2,0:0:0:0:
|
||||
40,192,52953,1,2,0:0:0:0:
|
||||
360,192,53125,1,8,0:0:0:0:
|
||||
40,192,53298,2,0,L|88:168,1,52.5
|
||||
240,192,53470,5,2,0:0:0:0:
|
||||
490,192,53642,2,0,L|418:192,1,52.5
|
||||
256,192,53815,1,8,0:0:0:0:
|
||||
112,192,53987,2,0,L|168:192,1,52.5
|
||||
328,192,54160,5,0,0:0:0:0:
|
||||
353,196,54203,1,0,0:0:0:0:
|
||||
378,188,54246,1,0,0:0:0:0:
|
||||
395,169,54289,1,0,0:0:0:0:
|
||||
400,144,54332,1,0,0:0:0:0:
|
||||
393,119,54375,1,0,0:0:0:0:
|
||||
374,101,54418,1,0,0:0:0:0:
|
||||
348,95,54461,1,0,0:0:0:0:
|
||||
216,192,54504,5,8,0:0:0:0:
|
||||
190,196,54547,1,0,0:0:0:0:
|
||||
165,188,54590,1,0,0:0:0:0:
|
||||
148,169,54633,1,0,0:0:0:0:
|
||||
143,144,54676,1,0,0:0:0:0:
|
||||
150,119,54719,1,0,0:0:0:0:
|
||||
169,101,54762,1,0,0:0:0:0:
|
||||
195,95,54805,1,0,0:0:0:0:
|
||||
72,192,54849,6,0,P|24:128|80:64,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,55194,1,8,0:0:0:0:
|
||||
448,192,55280,1,2,0:0:0:0:
|
||||
256,192,55367,1,2,0:0:0:0:
|
||||
64,192,55453,1,2,0:0:0:0:
|
||||
256,192,55539,2,0,L|320:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
116,192,55711,2,0,L|60:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,55884,2,0,L|320:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
116,192,56056,2,0,L|60:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
336,192,56229,6,0,L|496:192,1,157.500006008148,8|0,0:0|0:0,0:0:0:0:
|
||||
176,192,56573,2,0,L|16:192,1,157.500006008148,8|0,0:0|0:0,0:0:0:0:
|
||||
336,192,56918,5,8,0:0:0:0:
|
||||
410,190,57004,1,2,0:0:0:0:
|
||||
440,122,57090,1,2,0:0:0:0:
|
||||
390,66,57176,1,2,0:0:0:0:
|
||||
176,192,57263,2,0,P|96:112|168:48,1,236.250009012223,8|0,0:0|0:0,0:0:0:0:
|
||||
352,192,57608,6,0,L|456:192,1,105,6|0,0:0|0:0,0:0:0:0:
|
||||
192,192,57953,1,8,0:0:0:0:
|
||||
456,192,58125,2,0,L|400:192,1,52.5
|
||||
192,192,58298,2,0,L|192:136,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,58470,2,0,L|328:144,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
128,192,58642,1,8,0:0:0:0:
|
||||
72,192,58729,2,0,L|176:192,1,105
|
||||
328,192,58987,5,2,0:0:0:0:
|
||||
64,192,59160,2,0,L|8:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
177,192,59332,2,0,L|241:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
64,192,59504,2,0,L|8:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,59677,2,0,L|232:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
64,192,59849,2,0,L|8:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
192,192,60022,1,8,0:0:0:0:
|
||||
480,192,60194,2,0,L|432:168,1,52.5
|
||||
256,192,60367,6,0,L|368:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
71,192,60711,2,0,L|175:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
256,192,60970,1,0,0:0:0:0:
|
||||
441,192,61056,2,0,L|337:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
48,192,61401,1,8,0:0:0:0:
|
||||
30,172,61444,1,0,0:0:0:0:
|
||||
24,147,61487,1,0,0:0:0:0:
|
||||
30,122,61530,1,0,0:0:0:0:
|
||||
48,103,61573,1,0,0:0:0:0:
|
||||
320,192,61746,5,2,0:0:0:0:
|
||||
456,192,61918,1,2,0:0:0:0:
|
||||
192,192,62091,1,8,0:0:0:0:
|
||||
56,192,62263,1,2,0:0:0:0:
|
||||
320,192,62436,2,0,L|216:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
288,192,62694,1,2,0:0:0:0:
|
||||
448,192,62780,1,8,0:0:0:0:
|
||||
96,192,62953,1,2,0:0:0:0:
|
||||
384,192,63125,6,0,L|496:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
424,192,63384,1,0,0:0:0:0:
|
||||
176,192,63470,2,0,B|32:192|32:192|51:144|96:120,1,236.250009012223,8|0,0:0|0:0,0:0:0:0:
|
||||
240,192,63815,1,2,0:0:0:0:
|
||||
384,192,63901,1,2,0:0:0:0:
|
||||
240,192,63987,1,2,0:0:0:0:
|
||||
96,192,64073,1,2,0:0:0:0:
|
||||
288,192,64160,2,0,L|368:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
192,192,64332,2,0,L|96:192,1,78.7500030040742,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,64504,6,0,P|384:136|384:88,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
224,192,64849,2,0,L|160:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
328,192,65022,2,0,L|392:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
224,192,65194,2,0,L|160:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
344,192,65367,2,0,L|408:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
240,192,65539,2,0,L|160:192,1,78.7500030040742,8|0,0:0|0:0,0:0:0:0:
|
||||
16,192,65711,2,0,L|80:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,65884,6,0,B|416:192|416:192|384:144|320:120,1,236.250009012223,2|0,0:0|0:0,0:0:0:0:
|
||||
128,192,66229,1,8,0:0:0:0:
|
||||
48,192,66315,2,0,L|208:192,1,157.500006008148
|
||||
376,192,66573,1,8,0:0:0:0:
|
||||
205,192,66660,1,2,0:0:0:0:
|
||||
120,192,66746,2,0,L|176:192,1,52.5,2|2,0:0|0:0,0:0:0:0:
|
||||
336,192,66918,2,0,L|208:192,1,125.999996154785,8|2,0:0|0:0,0:0:0:0:
|
||||
480,192,67263,6,0,B|488:112|480:48|432:24|336:16|232:48,1,367.5,2|0,0:0|0:0,0:0:0:0:
|
||||
32,192,67953,2,0,B|24:112|32:48|80:24|176:16|280:48,1,367.5,2|0,0:0|0:0,0:0:0:0:
|
||||
120,192,68642,6,0,P|48:168|48:88,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
120,192,68987,2,0,P|248:120|248:96,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
96,192,69332,1,8,0:0:0:0:
|
||||
352,192,69504,2,0,L|408:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
248,192,69677,2,0,L|192:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,69849,2,0,L|408:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
232,192,70022,6,0,L|176:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
128,192,70194,1,2,0:0:0:0:
|
||||
264,192,70367,1,2,0:0:0:0:
|
||||
392,192,70539,2,0,L|328:192,1,52.5
|
||||
168,192,70711,5,8,0:0:0:0:
|
||||
142,199,70754,1,0,0:0:0:0:
|
||||
116,199,70797,1,0,0:0:0:0:
|
||||
91,193,70840,1,0,0:0:0:0:
|
||||
68,180,70883,1,0,0:0:0:0:
|
||||
50,162,70926,1,0,0:0:0:0:
|
||||
36,139,70969,1,0,0:0:0:0:
|
||||
30,114,71012,1,0,0:0:0:0:
|
||||
30,87,71055,1,0,0:0:0:0:
|
||||
37,62,71098,1,0,0:0:0:0:
|
||||
51,40,71142,1,0,0:0:0:0:
|
||||
70,22,71185,1,0,0:0:0:0:
|
||||
93,9,71228,1,0,0:0:0:0:
|
||||
118,4,71271,1,0,0:0:0:0:
|
||||
144,5,71314,1,0,0:0:0:0:
|
||||
169,12,71357,1,0,0:0:0:0:
|
||||
280,192,71401,6,0,P|392:120|368:72,1,210,2|2,0:0|0:0,0:0:0:0:
|
||||
160,192,72004,1,0,0:0:0:0:
|
||||
336,192,72091,1,8,0:0:0:0:
|
||||
283,192,72177,1,2,0:0:0:0:
|
||||
230,192,72263,1,2,0:0:0:0:
|
||||
177,192,72349,1,2,0:0:0:0:
|
||||
124,192,72435,1,2,0:0:0:0:
|
||||
88,192,72522,1,2,0:0:0:0:
|
||||
144,192,72608,1,2,0:0:0:0:
|
||||
224,192,72694,1,2,0:0:0:0:
|
||||
400,192,72780,6,0,L|504:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
416,192,73125,1,0,0:0:0:0:
|
||||
8,192,73470,2,0,L|120:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
32,192,73815,2,0,L|192:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
344,192,74160,6,0,L|456:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,74504,2,0,L|176:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
464,192,74849,2,0,L|408:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
256,192,75022,2,0,L|192:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,75194,2,0,L|296:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
144,192,75367,2,0,L|80:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
240,192,75539,6,0,L|176:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
40,192,75711,1,2,0:0:0:0:
|
||||
299,192,75884,2,0,L|352:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,76056,2,0,L|440:192,1,52.5
|
||||
280,192,76229,1,8,0:0:0:0:
|
||||
254,186,76272,1,0,0:0:0:0:
|
||||
235,168,76315,1,0,0:0:0:0:
|
||||
227,144,76358,1,0,0:0:0:0:
|
||||
232,118,76401,1,0,0:0:0:0:
|
||||
249,99,76444,1,0,0:0:0:0:
|
||||
274,90,76487,1,0,0:0:0:0:
|
||||
299,95,76530,1,0,0:0:0:0:
|
||||
408,192,76573,2,0,P|488:112|464:80,1,157.5
|
||||
288,192,76918,6,0,P|176:112|200:64,1,210,2|2,0:0|0:0,0:0:0:0:
|
||||
352,192,77522,1,0,0:0:0:0:
|
||||
208,192,77608,1,8,0:0:0:0:
|
||||
157,199,77694,1,2,0:0:0:0:
|
||||
112,173,77780,1,2,0:0:0:0:
|
||||
95,124,77866,1,2,0:0:0:0:
|
||||
111,76,77952,1,2,0:0:0:0:
|
||||
155,48,78039,1,2,0:0:0:0:
|
||||
240,192,78125,1,2,0:0:0:0:
|
||||
292,192,78211,1,2,0:0:0:0:
|
||||
144,192,78298,6,0,L|32:192,3,105,2|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
368,192,78987,2,0,L|480:192,3,105,2|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
136,192,79677,6,0,P|24:96|152:64,1,341.25,4|0,0:0|0:0,0:0:0:0:
|
||||
376,192,80367,2,0,P|488:96|360:64,1,341.25,8|0,0:0|0:0,0:0:0:0:
|
||||
96,192,81056,6,0,L|0:144,2,105,2|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
504,192,81746,1,8,0:0:0:0:
|
||||
336,192,82091,2,0,L|448:192,1,105
|
||||
160,192,82436,6,0,B|8:192|8:192|56:96|152:80,1,341.25,4|0,0:0|0:0,0:0:0:0:
|
||||
352,192,83125,2,0,B|504:192|504:192|456:96|360:80,1,341.25,8|0,0:0|0:0,0:0:0:0:
|
||||
224,192,83815,6,0,L|112:192,2,105,2|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
464,192,84504,1,8,0:0:0:0:
|
||||
336,192,84677,1,0,0:0:0:0:
|
||||
496,192,84849,1,0,0:0:0:0:
|
||||
336,192,85022,1,0,0:0:0:0:
|
||||
48,192,85194,6,0,L|272:192,2,210,4|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
464,192,86229,2,0,L|240:192,2,210,0|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
248,192,87263,2,0,L|136:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
264,192,87608,2,0,L|376:192,1,105
|
||||
104,192,87953,5,4,0:0:0:0:
|
||||
8,192,88125,2,0,L|64:192,1,52.5
|
||||
120,192,88298,2,0,L|32:136,1,105
|
||||
320,192,88642,1,8,0:0:0:0:
|
||||
424,192,88987,1,0,0:0:0:0:
|
||||
394,192,89101,1,0,0:0:0:0:
|
||||
324,192,89216,1,0,0:0:0:0:
|
||||
112,192,89332,6,0,L|224:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
384,192,89677,2,0,L|288:192,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
224,192,89907,1,0,0:0:0:0:
|
||||
48,192,90022,2,0,L|128:192,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
208,192,90252,1,0,0:0:0:0:
|
||||
280,192,90367,2,0,L|176:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,90625,1,0,0:0:0:0:
|
||||
416,192,90711,6,0,L|488:192,2,70,6|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
208,192,91056,1,0,0:0:0:0:
|
||||
181,192,91099,1,0,0:0:0:0:
|
||||
157,184,91142,1,0,0:0:0:0:
|
||||
136,168,91185,1,0,0:0:0:0:
|
||||
123,146,91228,1,0,0:0:0:0:
|
||||
118,120,91271,1,0,0:0:0:0:
|
||||
122,94,91314,1,0,0:0:0:0:
|
||||
135,72,91357,1,0,0:0:0:0:
|
||||
256,192,91401,6,0,L|336:192,2,70,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
168,192,91746,2,0,L|88:192,2,70
|
||||
384,192,92091,5,2,0:0:0:0:
|
||||
410,191,92134,1,0,0:0:0:0:
|
||||
434,181,92177,1,0,0:0:0:0:
|
||||
452,163,92220,1,0,0:0:0:0:
|
||||
424,192,92263,1,2,0:0:0:0:
|
||||
397,192,92306,1,0,0:0:0:0:
|
||||
373,182,92349,1,0,0:0:0:0:
|
||||
355,164,92392,1,0,0:0:0:0:
|
||||
232,192,92436,2,0,L|152:192,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
432,192,92780,6,0,L|504:192,2,70,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
337,192,93125,2,0,L|225:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,93470,6,0,L|408:192,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
320,192,93815,1,0,0:0:0:0:
|
||||
294,196,93858,1,0,0:0:0:0:
|
||||
268,191,93901,1,0,0:0:0:0:
|
||||
246,177,93944,1,0,0:0:0:0:
|
||||
231,156,93987,1,0,0:0:0:0:
|
||||
224,131,94030,1,0,0:0:0:0:
|
||||
226,105,94073,1,0,0:0:0:0:
|
||||
238,82,94116,1,0,0:0:0:0:
|
||||
352,192,94160,6,0,L|464:192,2,70,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
160,192,94504,2,0,L|48:192,2,70
|
||||
352,192,94849,5,2,0:0:0:0:
|
||||
325,192,94892,1,0,0:0:0:0:
|
||||
299,192,94935,1,0,0:0:0:0:
|
||||
288,192,94978,1,0,0:0:0:0:
|
||||
299,192,95022,1,2,0:0:0:0:
|
||||
325,192,95065,1,0,0:0:0:0:
|
||||
351,192,95108,1,0,0:0:0:0:
|
||||
377,192,95151,1,0,0:0:0:0:
|
||||
272,192,95194,2,0,L|217:144,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
72,192,95539,6,0,L|16:144,2,70,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
280,192,95884,2,0,L|392:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
96,192,96229,5,2,0:0:0:0:
|
||||
288,192,96344,1,0,0:0:0:0:
|
||||
488,192,96458,1,0,0:0:0:0:
|
||||
256,192,96573,1,0,0:0:0:0:
|
||||
229,193,96616,1,0,0:0:0:0:
|
||||
204,185,96659,1,0,0:0:0:0:
|
||||
183,170,96702,1,0,0:0:0:0:
|
||||
168,149,96745,1,0,0:0:0:0:
|
||||
160,124,96788,1,0,0:0:0:0:
|
||||
160,98,96831,1,0,0:0:0:0:
|
||||
169,74,96874,1,0,0:0:0:0:
|
||||
288,192,96918,5,8,0:0:0:0:
|
||||
480,192,97033,1,0,0:0:0:0:
|
||||
288,192,97147,1,0,0:0:0:0:
|
||||
80,192,97263,1,0,0:0:0:0:
|
||||
272,192,97378,1,0,0:0:0:0:
|
||||
464,192,97492,1,0,0:0:0:0:
|
||||
256,192,97608,5,2,0:0:0:0:
|
||||
232,183,97651,1,0,0:0:0:0:
|
||||
217,162,97694,1,0,0:0:0:0:
|
||||
224,136,97737,1,0,0:0:0:0:
|
||||
256,192,97780,1,2,0:0:0:0:
|
||||
281,183,97823,1,0,0:0:0:0:
|
||||
296,162,97866,1,0,0:0:0:0:
|
||||
297,136,97909,1,0,0:0:0:0:
|
||||
160,192,97953,2,0,L|80:192,2,70,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
352,192,98298,6,0,L|432:192,2,70,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
160,192,98642,2,0,L|40:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
296,192,98987,5,6,0:0:0:0:
|
||||
488,192,99102,1,0,0:0:0:0:
|
||||
296,192,99217,1,0,0:0:0:0:
|
||||
72,192,99332,1,0,0:0:0:0:
|
||||
46,189,99375,1,0,0:0:0:0:
|
||||
24,175,99418,1,0,0:0:0:0:
|
||||
10,153,99461,1,0,0:0:0:0:
|
||||
7,127,99504,1,0,0:0:0:0:
|
||||
17,103,99547,1,0,0:0:0:0:
|
||||
32,80,99590,1,0,0:0:0:0:
|
||||
48,72,99633,1,0,0:0:0:0:
|
||||
152,192,99677,5,2,0:0:0:0:
|
||||
328,192,99792,1,0,0:0:0:0:
|
||||
152,192,99907,1,0,0:0:0:0:
|
||||
440,192,100022,1,0,0:0:0:0:
|
||||
264,192,100137,1,0,0:0:0:0:
|
||||
88,192,100252,1,0,0:0:0:0:
|
||||
264,192,100367,5,4,0:0:0:0:
|
||||
296,192,100410,1,0,0:0:0:0:
|
||||
296,192,100453,1,0,0:0:0:0:
|
||||
264,192,100496,1,0,0:0:0:0:
|
||||
232,192,100539,1,0,0:0:0:0:
|
||||
208,192,100582,1,0,0:0:0:0:
|
||||
208,192,100625,1,0,0:0:0:0:
|
||||
232,192,100668,1,0,0:0:0:0:
|
||||
344,192,100711,1,2,0:0:0:0:
|
||||
152,192,101056,1,2,0:0:0:0:
|
||||
408,192,101401,1,2,0:0:0:0:
|
||||
64,192,101746,5,4,0:0:0:0:
|
||||
20,168,101832,1,0,0:0:0:0:
|
||||
24,119,101918,1,0,0:0:0:0:
|
||||
72,104,102004,1,0,0:0:0:0:
|
||||
160,192,102091,2,0,L|296:192,1,105
|
||||
416,192,102436,2,0,L|256:192,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
336,192,102780,2,0,L|400:192,1,52.5
|
||||
288,192,102953,2,0,L|224:192,1,52.5
|
||||
80,192,103125,6,0,L|184:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
464,192,103470,1,2,0:0:0:0:
|
||||
436,147,103556,1,0,0:0:0:0:
|
||||
396,113,103642,1,0,0:0:0:0:
|
||||
350,89,103728,1,0,0:0:0:0:
|
||||
300,73,103815,1,2,0:0:0:0:
|
||||
248,66,103901,1,0,0:0:0:0:
|
||||
195,67,103987,1,0,0:0:0:0:
|
||||
145,81,104073,1,0,0:0:0:0:
|
||||
110,118,104159,1,2,0:0:0:0:
|
||||
124,167,104245,1,0,0:0:0:0:
|
||||
158,207,104332,1,0,0:0:0:0:
|
||||
198,241,104418,1,0,0:0:0:0:
|
||||
360,192,104504,5,4,0:0:0:0:
|
||||
407,174,104590,1,0,0:0:0:0:
|
||||
426,127,104676,1,0,0:0:0:0:
|
||||
403,82,104762,1,0,0:0:0:0:
|
||||
208,192,104849,2,0,L|104:192,1,105
|
||||
384,192,105194,2,0,P|476:146|432:56,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
160,192,105711,1,0,0:0:0:0:
|
||||
456,192,105884,5,2,0:0:0:0:
|
||||
456,192,106142,1,0,0:0:0:0:
|
||||
272,192,106229,1,2,0:0:0:0:
|
||||
220,180,106315,1,0,0:0:0:0:
|
||||
170,164,106401,1,0,0:0:0:0:
|
||||
123,141,106487,1,0,0:0:0:0:
|
||||
91,101,106573,1,2,0:0:0:0:
|
||||
125,65,106660,1,0,0:0:0:0:
|
||||
175,51,106746,1,0,0:0:0:0:
|
||||
227,45,106832,1,0,0:0:0:0:
|
||||
280,42,106918,1,2,0:0:0:0:
|
||||
332,42,107004,1,0,0:0:0:0:
|
||||
385,44,107091,1,0,0:0:0:0:
|
||||
437,47,107177,1,0,0:0:0:0:
|
||||
240,192,107263,5,4,0:0:0:0:
|
||||
168,192,107349,1,0,0:0:0:0:
|
||||
216,192,107436,1,0,0:0:0:0:
|
||||
280,192,107522,1,0,0:0:0:0:
|
||||
440,192,107608,2,0,L|336:192,1,105
|
||||
32,192,107953,2,0,P|64:112|176:64,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
112,192,108470,1,0,0:0:0:0:
|
||||
384,192,108642,5,2,0:0:0:0:
|
||||
480,192,108815,2,0,L|424:192,1,52.5
|
||||
256,192,108987,1,2,0:0:0:0:
|
||||
200,192,109073,1,0,0:0:0:0:
|
||||
144,192,109159,1,0,0:0:0:0:
|
||||
98,192,109245,1,0,0:0:0:0:
|
||||
256,192,109332,1,2,0:0:0:0:
|
||||
308,192,109418,1,0,0:0:0:0:
|
||||
361,192,109504,1,0,0:0:0:0:
|
||||
413,192,109590,1,0,0:0:0:0:
|
||||
256,192,109677,1,2,0:0:0:0:
|
||||
16,192,109849,2,0,L|80:192,1,52.5
|
||||
256,192,110022,6,0,L|144:192,1,105,4|0,0:0|0:0,0:0:0:0:
|
||||
100,185,110280,1,0,0:0:0:0:
|
||||
67,146,110366,1,2,0:0:0:0:
|
||||
70,95,110452,1,0,0:0:0:0:
|
||||
107,59,110538,1,0,0:0:0:0:
|
||||
159,59,110625,1,0,0:0:0:0:
|
||||
328,192,110711,1,2,0:0:0:0:
|
||||
373,169,110797,1,0,0:0:0:0:
|
||||
382,119,110883,1,0,0:0:0:0:
|
||||
347,82,110969,1,0,0:0:0:0:
|
||||
256,192,111056,1,2,0:0:0:0:
|
||||
203,192,111142,1,0,0:0:0:0:
|
||||
151,192,111228,1,0,0:0:0:0:
|
||||
304,192,111315,1,0,0:0:0:0:
|
||||
464,192,111401,5,4,0:0:0:0:
|
||||
256,192,111573,12,0,112780,0:0:0:0:
|
||||
224,192,113125,6,0,L|448:192,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
16,192,113815,1,2,0:0:0:0:
|
||||
480,192,114160,6,0,L|152:192,1,315,2|0,0:0|0:0,0:0:0:0:
|
||||
32,192,114849,2,0,L|256:192,1,210,2|0,0:0|0:0,0:0:0:0:
|
||||
480,192,115539,2,0,B|360:96|136:104|152:200|112:288|112:288,2,420,2|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
400,192,117091,5,0,0:0:0:0:
|
||||
347,192,117177,1,0,0:0:0:0:
|
||||
295,192,117263,1,2,0:0:0:0:
|
||||
224,192,117349,1,0,0:0:0:0:
|
||||
62,192,117436,2,0,L|126:192,1,52.5
|
||||
277,192,117608,1,4,0:0:0:0:
|
||||
96,192,117953,2,0,P|16:120|32:96,1,157.5
|
||||
256,192,118298,6,0,P|360:80|328:48,1,236.250009012223,4|0,0:0|0:0,0:0:0:0:
|
||||
176,192,118642,1,8,0:0:0:0:
|
||||
440,192,118815,2,0,L|496:192,1,52.5
|
||||
336,192,118987,2,0,L|280:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
440,192,119160,2,0,L|496:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,119332,1,8,0:0:0:0:
|
||||
192,192,119418,2,0,L|280:128,1,105
|
||||
128,192,119677,5,2,0:0:0:0:
|
||||
416,192,119849,2,0,L|468:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
264,192,120022,2,0,L|320:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
464,192,120194,2,0,L|408:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
264,192,120367,2,0,L|320:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
488,192,120539,2,0,L|432:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,120711,2,0,L|216:192,1,62.9999980773926,8|0,0:0|0:0,0:0:0:0:
|
||||
65,192,120884,2,0,L|16:152,1,62.9999980773926,2|0,0:0|0:0,0:0:0:0:
|
||||
264,192,121056,6,0,P|344:120|336:80,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,121401,1,8,0:0:0:0:
|
||||
56,192,121573,1,0,0:0:0:0:
|
||||
328,192,121746,2,0,L|448:192,1,105,2|2,0:0|0:0,0:0:0:0:
|
||||
160,192,122091,1,8,0:0:0:0:
|
||||
186,192,122134,1,0,0:0:0:0:
|
||||
212,192,122177,1,0,0:0:0:0:
|
||||
238,192,122220,1,0,0:0:0:0:
|
||||
265,192,122263,1,0,0:0:0:0:
|
||||
16,192,122436,6,0,L|128:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
384,192,122780,2,0,L|272:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
16,192,123125,1,2,0:0:0:0:
|
||||
160,192,123298,1,2,0:0:0:0:
|
||||
160,192,123384,1,2,0:0:0:0:
|
||||
16,192,123470,1,8,0:0:0:0:
|
||||
288,192,123642,1,2,0:0:0:0:
|
||||
16,192,123815,6,0,L|128:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
384,192,124160,1,8,0:0:0:0:
|
||||
240,192,124332,1,0,0:0:0:0:
|
||||
496,192,124504,1,2,0:0:0:0:
|
||||
344,192,124591,1,2,0:0:0:0:
|
||||
192,192,124677,1,2,0:0:0:0:
|
||||
40,192,124763,1,2,0:0:0:0:
|
||||
344,192,124849,2,0,L|424:192,1,78.7500030040742,8|0,0:0|0:0,0:0:0:0:
|
||||
192,192,125022,2,0,L|136:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
336,192,125194,6,0,P|432:152|432:96,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
280,192,125539,2,0,L|200:192,1,78.7500030040742,8|0,0:0|0:0,0:0:0:0:
|
||||
56,192,125711,2,0,L|0:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,125884,2,0,L|257:192,1,78.7500030040742,2|0,0:0|0:0,0:0:0:0:
|
||||
400,192,126056,2,0,L|456:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,126229,2,0,L|208:192,1,78.7500030040742,8|0,0:0|0:0,0:0:0:0:
|
||||
56,192,126401,2,0,L|56:136,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,126573,6,0,P|208:136|136:176,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
288,192,126918,1,8,0:0:0:0:
|
||||
32,192,127091,2,0,L|32:128,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
176,192,127263,1,2,0:0:0:0:
|
||||
320,192,127349,1,2,0:0:0:0:
|
||||
176,192,127436,1,2,0:0:0:0:
|
||||
424,192,127608,2,0,L|424:88,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
152,192,127953,6,0,L|40:192,1,105,8|2,0:0|0:0,0:0:0:0:
|
||||
360,192,128298,2,0,L|472:192,1,105,8|2,0:0|0:0,0:0:0:0:
|
||||
128,192,128642,1,2,0:0:0:0:
|
||||
280,192,128729,1,2,0:0:0:0:
|
||||
424,192,128815,1,2,0:0:0:0:
|
||||
368,192,128901,1,2,0:0:0:0:
|
||||
192,192,128987,2,0,P|104:112|120:72,1,157.5,8|0,0:0|0:0,0:0:0:0:
|
||||
320,192,129332,6,0,P|408:112|392:72,1,157.5,6|0,0:0|0:0,0:0:0:0:
|
||||
216,192,129677,2,0,L|80:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
400,192,130022,2,0,L|456:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
304,192,130194,2,0,L|224:192,1,78.7500030040742,2|0,0:0|0:0,0:0:0:0:
|
||||
64,192,130367,2,0,L|112:160,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
272,192,130539,2,0,L|208:192,1,52.5
|
||||
64,192,130711,6,0,L|120:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
272,192,130884,2,0,L|352:192,1,78.7500030040742,2|0,0:0|0:0,0:0:0:0:
|
||||
496,192,131056,2,0,P|453:151|352:144,1,157.5,8|0,0:0|0:0,0:0:0:0:
|
||||
128,192,131401,1,0,0:0:0:0:
|
||||
102,192,131444,1,0,0:0:0:0:
|
||||
79,180,131487,1,0,0:0:0:0:
|
||||
65,158,131530,1,0,0:0:0:0:
|
||||
64,132,131573,1,0,0:0:0:0:
|
||||
77,109,131616,1,0,0:0:0:0:
|
||||
99,96,131659,1,0,0:0:0:0:
|
||||
125,95,131702,1,0,0:0:0:0:
|
||||
232,192,131746,1,8,0:0:0:0:
|
||||
328,192,131918,1,2,0:0:0:0:
|
||||
48,192,132091,6,0,P|112:112|176:104,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
328,192,132436,1,8,0:0:0:0:
|
||||
64,192,132608,1,2,0:0:0:0:
|
||||
480,192,132780,1,2,0:0:0:0:
|
||||
328,192,132867,1,0,0:0:0:0:
|
||||
176,192,132953,1,2,0:0:0:0:
|
||||
24,192,133039,1,0,0:0:0:0:
|
||||
176,192,133125,1,8,0:0:0:0:
|
||||
328,192,133212,1,0,0:0:0:0:
|
||||
176,192,133298,1,2,0:0:0:0:
|
||||
24,192,133384,1,0,0:0:0:0:
|
||||
256,192,133470,5,0,0:0:0:0:
|
||||
292,206,133513,1,0,0:0:0:0:
|
||||
330,200,133556,1,0,0:0:0:0:
|
||||
361,176,133599,1,0,0:0:0:0:
|
||||
375,140,133642,1,0,0:0:0:0:
|
||||
370,101,133685,1,0,0:0:0:0:
|
||||
346,70,133728,1,0,0:0:0:0:
|
||||
192,192,133815,2,0,L|88:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
368,192,134160,1,0,0:0:0:0:
|
||||
405,183,134203,1,0,0:0:0:0:
|
||||
434,157,134246,1,0,0:0:0:0:
|
||||
448,120,134289,1,0,0:0:0:0:
|
||||
442,82,134332,1,0,0:0:0:0:
|
||||
288,192,134418,1,0,0:0:0:0:
|
||||
136,192,134504,2,0,P|144:136|192:112,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
448,192,134849,6,0,P|480:120|416:80,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
208,192,135194,1,8,0:0:0:0:
|
||||
80,192,135367,1,2,0:0:0:0:
|
||||
352,192,135539,2,0,L|296:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
155,192,135711,2,0,L|211:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
384,192,135884,2,0,L|328:192,1,52.5,8|0,0:0|0:0,0:0:0:0:
|
||||
187,192,136056,2,0,L|243:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
384,192,136229,6,0,P|440:136|424:112,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
144,192,136573,1,8,0:0:0:0:
|
||||
48,192,136746,2,0,L|104:192,1,52.5
|
||||
256,192,136918,2,0,L|296:192,7,26.25,2|0|0|0|0|0|0|0,0:0|0:0|0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
160,192,137263,2,0,L|48:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
368,192,137608,6,0,P|448:112|432:88,1,157.5,2|0,0:0|0:0,0:0:0:0:
|
||||
256,192,137953,2,0,L|144:192,1,105,8|0,0:0|0:0,0:0:0:0:
|
||||
480,192,138298,1,2,0:0:0:0:
|
||||
328,192,138384,1,2,0:0:0:0:
|
||||
176,192,138470,1,2,0:0:0:0:
|
||||
96,192,138556,1,2,0:0:0:0:
|
||||
256,192,138642,1,8,0:0:0:0:
|
||||
400,192,138729,1,0,0:0:0:0:
|
||||
216,192,138815,2,0,L|152:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
352,192,138987,6,0,L|496:120,3,157.500006008148,2|0|0|0,0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
192,192,139677,2,0,L|56:192,1,125.999996154785,2|0,0:0|0:0,0:0:0:0:
|
||||
368,192,140022,2,0,L|256:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
16,192,140367,5,2,0:0:0:0:
|
||||
48,151,140453,1,0,0:0:0:0:
|
||||
94,125,140539,1,0,0:0:0:0:
|
||||
145,118,140625,1,0,0:0:0:0:
|
||||
304,192,140711,2,0,L|416:192,1,105
|
||||
120,192,141056,2,0,P|48:112|104:64,1,157.5,4|0,0:0|0:0,0:0:0:0:
|
||||
160,192,141401,1,0,0:0:0:0:
|
||||
212,192,141487,1,0,0:0:0:0:
|
||||
265,192,141573,1,0,0:0:0:0:
|
||||
317,192,141659,1,0,0:0:0:0:
|
||||
160,192,141746,5,2,0:0:0:0:
|
||||
212,192,141832,1,0,0:0:0:0:
|
||||
265,192,141918,1,0,0:0:0:0:
|
||||
317,192,142004,1,0,0:0:0:0:
|
||||
160,192,142091,2,0,L|56:192,1,105
|
||||
317,192,142436,2,0,P|432:128|432:80,1,157.5,4|0,0:0|0:0,0:0:0:0:
|
||||
256,192,142780,2,0,L|179:192,1,52.5
|
||||
152,192,142953,2,0,L|216:192,1,52.5
|
||||
376,192,143125,5,2,0:0:0:0:
|
||||
425,182,143211,1,0,0:0:0:0:
|
||||
448,137,143297,1,0,0:0:0:0:
|
||||
426,91,143383,1,0,0:0:0:0:
|
||||
256,192,143470,2,0,L|360:192,1,105,2|0,0:0|0:0,0:0:0:0:
|
||||
88,192,143815,2,0,P|32:112|72:72,1,157.5,4|0,0:0|0:0,0:0:0:0:
|
||||
216,192,144160,2,0,L|280:192,1,52.5,2|0,0:0|0:0,0:0:0:0:
|
||||
112,192,144332,2,0,L|48:192,1,52.5
|
||||
288,192,144504,5,2,0:0:0:0:
|
||||
448,192,144677,1,0,0:0:0:0:
|
||||
471,146,144763,1,0,0:0:0:0:
|
||||
464,94,144849,1,2,0:0:0:0:
|
||||
428,57,144935,1,2,0:0:0:0:
|
||||
377,47,145022,1,2,0:0:0:0:
|
||||
330,69,145108,1,2,0:0:0:0:
|
||||
64,192,145194,1,4,0:0:0:0:
|
||||
296,192,145884,5,0,0:0:0:0:
|
||||
128,192,146229,1,2,0:0:0:0:
|
||||
280,192,146573,1,0,0:0:0:0:
|
||||
56,192,146918,1,2,0:0:0:0:
|
||||
480,192,147263,5,2,0:0:0:0:
|
||||
288,192,147780,1,0,0:0:0:0:
|
||||
432,192,147953,1,2,0:0:0:0:
|
||||
160,192,148298,1,0,0:0:0:0:
|
||||
472,192,148642,5,2,0:0:0:0:
|
||||
128,192,149332,1,2,0:0:0:0:
|
||||
488,192,150022,1,2,0:0:0:0:
|
||||
16,192,150367,5,2,0:0:0:0:
|
||||
488,192,150711,1,4,0:0:0:0:
|
31
pp/catch_the_pp/sample.py
Normal file
31
pp/catch_the_pp/sample.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .osu_parser.beatmap import Beatmap
|
||||
from .osu.ctb.difficulty import Difficulty
|
||||
from .ppCalc import calculate_pp
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
beatmap = Beatmap(os.path.dirname(os.path.realpath(__file__)) + "/test.osu") # Yes... this be my test file (Will remove when project is done)
|
||||
else:
|
||||
beatmap = Beatmap(sys.argv[1])
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
mods = int(sys.argv[2])
|
||||
else:
|
||||
mods = 0
|
||||
|
||||
difficulty = Difficulty(beatmap, mods)
|
||||
print("Calculation:")
|
||||
print("Stars: {}, PP: {}, MaxCombo: {}\n".format(
|
||||
difficulty.star_rating, calculate_pp(difficulty, 1, beatmap.max_combo, 0), beatmap.max_combo
|
||||
))
|
||||
|
||||
"""
|
||||
m = {"NOMOD": 0, "EASY": 2, "HIDDEN": 8, "HARDROCK": 16, "DOUBLETIME": 64, "HALFTIME": 256, "FLASHLIGHT": 1024}
|
||||
for key in m.keys():
|
||||
difficulty = Difficulty(beatmap, m[key])
|
||||
print("Mods: {}".format(key))
|
||||
print("Stars: {}".format(difficulty.star_rating))
|
||||
print("PP: {}\n".format(calculate_pp(difficulty, 1, beatmap.max_combo, 0)))
|
||||
"""
|
16
pp/catch_the_pp/setup.py
Normal file
16
pp/catch_the_pp/setup.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from distutils.core import setup
|
||||
from distutils.extension import Extension
|
||||
from Cython.Build import cythonize
|
||||
import os
|
||||
|
||||
extensions = []
|
||||
for root, dirs, files in os.walk(os.getcwd()):
|
||||
for file in files:
|
||||
if file.endswith(".pyx"):
|
||||
file_path = os.path.relpath(os.path.join(root, file))
|
||||
extensions.append(Extension(file_path.replace("/", ".")[:-4], [file_path]))
|
||||
|
||||
setup(
|
||||
name="catch-the-pp",
|
||||
ext_modules=cythonize(extensions, nthreads=4),
|
||||
)
|
1219
pp/catch_the_pp/test.osu
Normal file
1219
pp/catch_the_pp/test.osu
Normal file
File diff suppressed because it is too large
Load Diff
82
pp/cicciobello.py
Normal file
82
pp/cicciobello.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from common.log import logUtils as log
|
||||
from common.constants import gameModes
|
||||
from constants import exceptions
|
||||
from helpers import mapsHelper
|
||||
|
||||
from pp.catch_the_pp.osu_parser.beatmap import Beatmap as CalcBeatmap
|
||||
from pp.catch_the_pp.osu.ctb.difficulty import Difficulty
|
||||
from pp.catch_the_pp import ppCalc
|
||||
|
||||
|
||||
class Cicciobello:
|
||||
def __init__(self, _beatmap, _score=None, accuracy=0, mods=0, combo=-1, misses=0, tillerino=False):
|
||||
# Beatmap is always present
|
||||
self.beatmap = _beatmap
|
||||
|
||||
# If passed, set everything from score object
|
||||
if _score is not None:
|
||||
self.score = _score
|
||||
self.accuracy = self.score.accuracy
|
||||
self.mods = self.score.mods
|
||||
self.combo = self.score.maxCombo
|
||||
self.misses = self.score.cMiss
|
||||
else:
|
||||
# Otherwise, set acc and mods from params (tillerino)
|
||||
self.accuracy = accuracy
|
||||
self.mods = mods
|
||||
self.combo = combo
|
||||
if self.combo < 0:
|
||||
self.combo = self.beatmap.maxCombo
|
||||
self.misses = misses
|
||||
|
||||
# Multiple acc values computation
|
||||
self.tillerino = tillerino
|
||||
|
||||
# Result
|
||||
self.pp = 0
|
||||
self.calculate_pp()
|
||||
|
||||
def calculate_pp(self):
|
||||
try:
|
||||
# Cache beatmap
|
||||
mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID)
|
||||
mapsHelper.cacheMap(mapFile, self.beatmap)
|
||||
|
||||
# TODO: Sanizite mods
|
||||
|
||||
# Gamemode check
|
||||
if self.score and self.score.gameMode != gameModes.CTB:
|
||||
raise exceptions.unsupportedGameModeException()
|
||||
|
||||
# Accuracy check
|
||||
if self.accuracy > 1:
|
||||
raise ValueError("Accuracy must be between 0 and 1")
|
||||
|
||||
# Calculate difficulty
|
||||
calcBeatmap = CalcBeatmap(mapFile)
|
||||
difficulty = Difficulty(beatmap=calcBeatmap, mods=self.mods)
|
||||
|
||||
# Calculate pp
|
||||
if self.tillerino:
|
||||
results = []
|
||||
for acc in [1, 0.99, 0.98, 0.95]:
|
||||
results.append(ppCalc.calculate_pp(
|
||||
diff=difficulty, accuracy=acc, combo=self.combo, miss=self.misses
|
||||
))
|
||||
self.pp = results
|
||||
else:
|
||||
self.pp = ppCalc.calculate_pp(
|
||||
diff=difficulty, accuracy=self.accuracy, combo=self.combo, miss=self.misses
|
||||
)
|
||||
except exceptions.osuApiFailException:
|
||||
log.error("cicciobello ~> osu!api error!")
|
||||
self.pp = 0
|
||||
except exceptions.unsupportedGameModeException:
|
||||
log.error("cicciobello ~> Unsupported gamemode")
|
||||
self.pp = 0
|
||||
except Exception as e:
|
||||
log.error("cicciobello ~> Unhandled exception: {}".format(str(e)))
|
||||
self.pp = 0
|
||||
raise
|
||||
finally:
|
||||
log.debug("cicciobello ~> Shutting down, pp = {}".format(self.pp))
|
BIN
pp/cmyui-testing/CAy.rar
Normal file
BIN
pp/cmyui-testing/CAy.rar
Normal file
Binary file not shown.
BIN
pp/cmyui-testing/TiS.zip
Normal file
BIN
pp/cmyui-testing/TiS.zip
Normal file
Binary file not shown.
11
pp/oppai-ng/.gitignore
vendored
Normal file
11
pp/oppai-ng/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
tags
|
||||
*.log
|
||||
*.tar.xz
|
||||
*.zip
|
||||
/oppai
|
||||
*.json
|
||||
*.obj
|
||||
*.exe
|
||||
*.swp
|
||||
/test/test_suite
|
||||
/test/oppai_test
|
22
pp/oppai-ng/.travis.yml
Normal file
22
pp/oppai-ng/.travis.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
language: c
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: precise
|
||||
- os: linux
|
||||
dist: trusty
|
||||
- os: osx
|
||||
|
||||
|
||||
install: true
|
||||
cache:
|
||||
directories:
|
||||
- test/test_suite
|
||||
|
||||
script:
|
||||
- ./build
|
||||
- cd test
|
||||
- ./download_suite
|
||||
- ./build
|
||||
- ./oppai_test
|
349
pp/oppai-ng/README.md
Normal file
349
pp/oppai-ng/README.md
Normal file
@@ -0,0 +1,349 @@
|
||||
[](https://travis-ci.org/Francesco149/oppai-ng)
|
||||
|
||||
difficulty and pp calculator for osu!
|
||||
|
||||
this is a pure C89 rewrite of
|
||||
[oppai](https://github.com/Francesco149/oppai) with much lower
|
||||
memory usage, smaller and easier to read codebase
|
||||
executable size and better performance.
|
||||
|
||||
experimental taiko support is now available and appears to give
|
||||
correct values for actual taiko maps. converted maps are still
|
||||
unreliable due to incorrect slider conversion and might be
|
||||
completely off (use ```-m1``` or ```-taiko``` to convert a std map
|
||||
to taiko).
|
||||
|
||||
- [installing (linux)](#installing-linux)
|
||||
- [installing (windows)](#installing-windows)
|
||||
- [installing (osx)](#installing-osx)
|
||||
- [usage](#usage)
|
||||
- [implementations for other programming languages](#implementations-for-other-programming-languages)
|
||||
- [oppai-ng vs old oppai](#oppai-ng-vs-old-oppai)
|
||||
- [compile from source (windows)](#compile-from-source-windows)
|
||||
- [using oppai as a library or making bindings](#using-oppai-as-a-library-or-making-bindings)
|
||||
- [other build parameters](#other-build-parameters)
|
||||
|
||||
# installing (linux)
|
||||
```sh
|
||||
wget https://github.com/Francesco149/oppai-ng/archive/HEAD.tar.gz
|
||||
tar xf HEAD.tar.gz
|
||||
cd oppai-*
|
||||
./build
|
||||
sudo install -Dm 755 oppai /usr/bin/oppai
|
||||
|
||||
oppai
|
||||
```
|
||||
|
||||
you can also grab pre-compiled standalone binaries (statically
|
||||
linked against musl libc) from
|
||||
[here](https://github.com/Francesco149/oppai-ng/releases) if you
|
||||
are somehow too scared to run those 5 commands.
|
||||
|
||||
# installing (windows)
|
||||
download and unzip binaries from
|
||||
[here](https://github.com/Francesco149/oppai-ng/releases) and
|
||||
optionally add oppai's folder to your ```PATH``` environment
|
||||
variable for easy access. you can find a guide
|
||||
[here](https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/)
|
||||
if you don't know how.
|
||||
|
||||
# installing (osx)
|
||||
## via homebrew
|
||||
```sh
|
||||
brew install --HEAD pmrowla/homebrew-tap/oppai-ng
|
||||
```
|
||||
Note that installing with ```--HEAD``` is recommended but not required.
|
||||
Installing from homebrew will place the ```oppai``` executable in your homebrew path.
|
||||
|
||||
## manually
|
||||
Follow the same steps as for linux but substitute ```curl -O``` for ```wget``` since wget is not distributed by default in osx.
|
||||
The same caveat applies if you want to run the test suite - you will need to edit the ```download_suite``` script to use curl.
|
||||
|
||||
# usage
|
||||
you can run oppai with no arguments to check the documentation.
|
||||
|
||||
here's some example usages:
|
||||
|
||||
```sh
|
||||
oppai path/to/map.osu +HDHR 98% 500x 1xmiss
|
||||
oppai path/to/map.osu 3x100
|
||||
oppai path/to/map.osu 3x100 OD10
|
||||
oppai path/to/map.osu -ojson
|
||||
```
|
||||
|
||||
you can also pipe maps from standard input by setting the filename
|
||||
to ```-```.
|
||||
|
||||
for example on linux you can do:
|
||||
|
||||
```sh
|
||||
curl https://osu.ppy.sh/osu/774965 | oppai - +HDDT
|
||||
curl https://osu.ppy.sh/osu/774965 | oppai - +HDDT 1200x 1m
|
||||
```
|
||||
|
||||
while on windows it's a bit more verbose (powershell):
|
||||
|
||||
```powershell
|
||||
(New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai -
|
||||
(New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - +HDHR
|
||||
(New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - +HDHR 99% 600x 1m
|
||||
```
|
||||
|
||||
I got the .osu file url from "Grab latest .osu file" on the
|
||||
beatmap's page.
|
||||
|
||||
# implementations for other programming languages
|
||||
oppai has been implemented for many other programming languages.
|
||||
If you feel like making your own implementation and want it listed
|
||||
here, open an issue or pull request. the requirement is that it
|
||||
should pass the same test suite that oppai-ng passes.
|
||||
|
||||
note: these aren't just native bindings unless stated otherwise.
|
||||
|
||||
* [ojsama (javascript)](https://github.com/Francesco149/ojsama)
|
||||
* [koohii (java)](https://github.com/Francesco149/koohii) . this
|
||||
is currently being used in tillerino.
|
||||
* [pyttanko (python)](https://github.com/Francesco149/pyttanko)
|
||||
* [oppai5 (golang)](https://github.com/flesnuk/oppai5) (by flesnuk)
|
||||
* [OppaiSharp (C#)](https://github.com/HoLLy-HaCKeR/OppaiSharp)
|
||||
(by HoLLy)
|
||||
|
||||
# oppai-ng vs old oppai
|
||||
executable size is around 7 times smaller:
|
||||
```sh
|
||||
$ cd ~/src/oppai
|
||||
$ ./build.sh -static
|
||||
$ wc -c oppai
|
||||
574648 oppai
|
||||
|
||||
$ cd ~/src/oppai-ng
|
||||
$ ./build -static
|
||||
$ wc -c oppai
|
||||
75512 oppai
|
||||
```
|
||||
|
||||
oppai-ng has proper error output in whatever format you select,
|
||||
while legacy oppai either gives empty output or just dies with
|
||||
a plaintext error.
|
||||
|
||||
oppai-ng has well-defined errno style error codes that you can
|
||||
check for when using it as a library or reading its output.
|
||||
|
||||
the same test suite runs about 45% faster on oppai-ng compared
|
||||
to old oppai, also the peak resident memory size is 4 to 6 times
|
||||
smaller according to various ```time -v``` runs.
|
||||
|
||||
```sh
|
||||
$ cd ~/src/oppai
|
||||
$ ./build_test.sh
|
||||
$ time -v ./oppai_test
|
||||
...
|
||||
Command being timed: "./oppai_test"
|
||||
User time (seconds): 13.89
|
||||
System time (seconds): 0.10
|
||||
Percent of CPU this job got: 99%
|
||||
Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 13.99s
|
||||
Average shared text size (kbytes): 0
|
||||
Average unshared data size (kbytes): 0
|
||||
Average stack size (kbytes): 0
|
||||
Average total size (kbytes): 0
|
||||
Maximum resident set size (kbytes): 45184
|
||||
Average resident set size (kbytes): 0
|
||||
Major (requiring I/O) page faults: 0
|
||||
Minor (reclaiming a frame) page faults: 2143
|
||||
Voluntary context switches: 1
|
||||
Involuntary context switches: 41
|
||||
Swaps: 0
|
||||
File system inputs: 0
|
||||
File system outputs: 0
|
||||
Socket messages sent: 0
|
||||
Socket messages received: 0
|
||||
Signals delivered: 0
|
||||
Page size (bytes): 4096
|
||||
Exit status: 0
|
||||
|
||||
$ cd ~/src/oppai-ng/test/
|
||||
$ ./build
|
||||
$ time -v ./oppai_test
|
||||
...
|
||||
Command being timed: "./oppai_test"
|
||||
User time (seconds): 9.09
|
||||
System time (seconds): 0.06
|
||||
Percent of CPU this job got: 99%
|
||||
Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 9.15s
|
||||
Average shared text size (kbytes): 0
|
||||
Average unshared data size (kbytes): 0
|
||||
Average stack size (kbytes): 0
|
||||
Average total size (kbytes): 0
|
||||
Maximum resident set size (kbytes): 11840
|
||||
Average resident set size (kbytes): 0
|
||||
Major (requiring I/O) page faults: 0
|
||||
Minor (reclaiming a frame) page faults: 304
|
||||
Voluntary context switches: 1
|
||||
Involuntary context switches: 39
|
||||
Swaps: 0
|
||||
File system inputs: 0
|
||||
File system outputs: 0
|
||||
Socket messages sent: 0
|
||||
Socket messages received: 0
|
||||
Signals delivered: 0
|
||||
Page size (bytes): 4096
|
||||
Exit status: 0
|
||||
```
|
||||
|
||||
note that when the test suite is compiled without libcurl, the
|
||||
resident memory usage drops by a flat 4mb, so almost half of that
|
||||
is curl.
|
||||
|
||||
you can expect oppai memory usage to be under 4 mb most of the time
|
||||
with the raw parsed beatmap data not taking more than ~800k even
|
||||
for a 15 minute marathon.
|
||||
|
||||
the codebase has ~3-4x less lines than legacy oppai, making it easy
|
||||
to read and use as a single header library. not only it is smaller,
|
||||
but it now also implements both taiko and osu, so more features
|
||||
than legacy oppai.
|
||||
|
||||
the osu! pp and diff calc alone would be around ~3k LOC including
|
||||
the cli, which would be 5x less lines than legacy oppai for the
|
||||
same functionality.
|
||||
|
||||
```sh
|
||||
$ cd ~/src/oppai
|
||||
$ sloc *.cc
|
||||
|
||||
---------- Result ------------
|
||||
|
||||
Physical : 15310
|
||||
Source : 14406
|
||||
Comment : 301
|
||||
Single-line comment : 289
|
||||
Block comment : 12
|
||||
Mixed : 23
|
||||
Empty : 626
|
||||
To Do : 11
|
||||
|
||||
Number of files read : 10
|
||||
|
||||
------------------------------
|
||||
|
||||
$ cd ~/src/oppai-ng
|
||||
$ sloc *.c
|
||||
|
||||
---------- Result ------------
|
||||
|
||||
Physical : 4123
|
||||
Source : 2906
|
||||
Comment : 492
|
||||
Single-line comment : 1
|
||||
Block comment : 491
|
||||
Mixed : 64
|
||||
Empty : 811
|
||||
To Do : 9
|
||||
|
||||
Number of files read : 2
|
||||
|
||||
------------------------------
|
||||
```
|
||||
|
||||
not to mention it's C89, which will be compatible with many more
|
||||
platforms and old compilers than c++98
|
||||
|
||||
```oppai.c``` alone is only ~2200 LOC (~1500 without comments), and
|
||||
you can compile piece of it out when you don't need them.
|
||||
|
||||
of course, it's not as heavily tested as legacy oppai (which runs
|
||||
24/7 on Tillerino's back-end), however the test suite is a very
|
||||
good test that runs through ~12000 unique scores and I'm confident
|
||||
this rewrite is already very stable.
|
||||
|
||||
# compile from source (windows)
|
||||
oppai should compile even on old versions of msvc dating back to
|
||||
2005, although it was only tested on msvc 2010 and higher.
|
||||
|
||||
have at least [microsoft c++ build tools](http://landinghub.visualstudio.com/visual-cpp-build-tools)
|
||||
installed. visual studio with c/c++ support also works.
|
||||
|
||||
open a visual studio prompt:
|
||||
|
||||
```bat
|
||||
cd path\to\oppai\source
|
||||
build.bat
|
||||
oppai
|
||||
```
|
||||
|
||||
you can also probably set up mingw and cygwin and follow the linux
|
||||
instructions instead, I'm not sure. I don't use windows.
|
||||
|
||||
# using oppai as a library or making bindings
|
||||
the new codebase is much easier to isolate and include in your
|
||||
projects.
|
||||
|
||||
just copy oppai.c into your project, it acts as a single-header
|
||||
library.
|
||||
|
||||
```c
|
||||
#define OPPAI_IMPLEMENTATION
|
||||
#include "../oppai.c"
|
||||
|
||||
int main()
|
||||
{
|
||||
struct parser pstate;
|
||||
struct beatmap map;
|
||||
|
||||
uint32_t mods;
|
||||
struct diff_calc stars;
|
||||
struct pp_calc pp;
|
||||
|
||||
p_init(&pstate);
|
||||
p_map(&pstate, &map, stdin);
|
||||
|
||||
mods = MODS_HD | MODS_DT;
|
||||
|
||||
d_init(&stars);
|
||||
d_calc(&stars, &map, mods);
|
||||
printf("%g stars\n", stars.total);
|
||||
|
||||
b_ppv2(&map, &pp, stars.aim, stars.speed, mods);
|
||||
printf("%gpp\n", pp.total);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
gcc test.c
|
||||
cat /path/to/file.osu | ./a.out
|
||||
```
|
||||
|
||||
read oppai.c, there's documentation for each function at the top.
|
||||
|
||||
see examples directory for detailed examples. you can also read
|
||||
main.c to see how the CLI uses it.
|
||||
|
||||
oppai is also modular, you can define out parts of the code
|
||||
that you don't use by defining any of:
|
||||
```
|
||||
OPPAI_NOPARSER
|
||||
OPPAI_NOPP
|
||||
OPPAI_NODIFFCALC
|
||||
```
|
||||
|
||||
if you don't feel comfortable writing bindings or using oppai
|
||||
from c code, you can use the -o parameter to output in json or
|
||||
other parsable formats. ```examples/binary.c``` shows how to parse
|
||||
the binary output.
|
||||
|
||||
# other build parameters
|
||||
when you build the oppai cli, you can pass any of these parameters
|
||||
to the build script to disable features:
|
||||
|
||||
* ```-DOPPAI_NOTEXT``` disables text output module
|
||||
* ```-DOPPAI_NOJSON``` disables json output module
|
||||
* ```-DOPPAI_NOCSV``` disables CSV output module
|
||||
* ```-DOPPAI_NOBINARY``` disables binary output module
|
||||
* ```-DOPPAI_DEBUG``` enables debug output module and memory usage
|
||||
statistics
|
||||
* ```-DOPPAI_NOSTDINT``` doesn't use ```stdint.h```, as some
|
||||
machines or old compilers don't have it
|
||||
|
24
pp/oppai-ng/UNLICENSE
Normal file
24
pp/oppai-ng/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
11
pp/oppai-ng/build.bat
Normal file
11
pp/oppai-ng/build.bat
Normal file
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
|
||||
del oppai.exe >nul 2>&1
|
||||
del oppai.obj >nul 2>&1
|
||||
cl -D_CRT_SECURE_NO_WARNINGS=1 ^
|
||||
-DNOMINMAX=1 ^
|
||||
-O2 ^
|
||||
-nologo -MT -Gm- -GR- -EHsc -W4 ^
|
||||
main.c ^
|
||||
-Feoppai.exe ^
|
||||
|| EXIT /B 1
|
10
pp/oppai-ng/build_containers
Normal file
10
pp/oppai-ng/build_containers
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
dir=$(dirname $0)
|
||||
for d in $dir/docker/*
|
||||
do
|
||||
bn=$(basename $d)
|
||||
cp $d/Dockerfile .
|
||||
docker build -t "oppai-ng:$bn" .
|
||||
rm Dockerfile
|
||||
done
|
7
pp/oppai-ng/build_containers.ps1
Normal file
7
pp/oppai-ng/build_containers.ps1
Normal file
@@ -0,0 +1,7 @@
|
||||
$dir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
Push-Location "$dir"
|
||||
$d = "docker/windows"
|
||||
Copy-Item $d/Dockerfile .
|
||||
docker build -t "oppai-ng:windows" .
|
||||
Remove-Item Dockerfile
|
||||
Pop-Location
|
34
pp/oppai-ng/cflags
Normal file
34
pp/oppai-ng/cflags
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
cflags="-std=c89 -pedantic"
|
||||
cflags="$cflags -O3"
|
||||
cflags="$cflags -fno-strict-aliasing"
|
||||
cflags="$cflags -Wno-variadic-macros -Wno-long-long -Wall"
|
||||
cflags="$cflags -ffunction-sections -fdata-sections"
|
||||
cflags="$cflags -g0 -fno-unwind-tables -s"
|
||||
cflags="$cflags -fno-asynchronous-unwind-tables"
|
||||
|
||||
ldflags="-lm"
|
||||
|
||||
cflags="$cflags $CFLAGS"
|
||||
ldflags="$ldflags $LDFLAGS"
|
||||
|
||||
cc="$CC"
|
||||
|
||||
if [ $(uname) = "Darwin" ]; then
|
||||
cc=${cc:-clang}
|
||||
else
|
||||
cc=${cc:-gcc}
|
||||
fi
|
||||
|
||||
uname -a > flags.log
|
||||
echo $cc >> flags.log
|
||||
echo $cflags >> flags.log
|
||||
echo $ldflags >> flags.log
|
||||
$cc --version >> flags.log
|
||||
$cc -dumpmachine >> flags.log
|
||||
|
||||
export cflags="$cflags"
|
||||
export ldflags="$ldflags"
|
||||
export cc="$cc"
|
||||
|
5
pp/oppai-ng/docker/musl-x86/Dockerfile
Normal file
5
pp/oppai-ng/docker/musl-x86/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM multiarch/alpine:x86-latest-stable
|
||||
|
||||
RUN apk add --no-cache musl-dev gcc git xz
|
||||
WORKDIR /tmp
|
||||
CMD [ "sh", "./release" ]
|
5
pp/oppai-ng/docker/musl-x86_64/Dockerfile
Normal file
5
pp/oppai-ng/docker/musl-x86_64/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM multiarch/alpine:x86_64-latest-stable
|
||||
|
||||
RUN apk add --no-cache musl-dev gcc git xz
|
||||
WORKDIR /tmp
|
||||
CMD [ "sh", "./release" ]
|
26
pp/oppai-ng/docker/windows/Dockerfile
Normal file
26
pp/oppai-ng/docker/windows/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
# escape=`
|
||||
|
||||
# I wanted to use nanoserver but I couldn't get visual c++ build
|
||||
# tools to install. the nuget visual c++ build tools package isn't
|
||||
# enough to get a working compiler and manually copying msvc
|
||||
# defeats the purpose of using the container to automatically
|
||||
# install deps.
|
||||
# unfortunately windowsservercore is a 4GB image
|
||||
FROM microsoft/windowsservercore
|
||||
|
||||
RUN @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
|
||||
|
||||
# wait for vs_installer.exe, vs_installerservice.exe
|
||||
# or vs_installershell.exe because choco doesn't
|
||||
RUN powershell -NoProfile -InputFormat None -Command `
|
||||
choco install git 7zip -y; `
|
||||
choco install visualcpp-build-tools `
|
||||
--version 15.0.26228.20170424 -y; `
|
||||
Write-Host 'Waiting for Visual C++ Build Tools to finish'; `
|
||||
Wait-Process -Name vs_installer
|
||||
|
||||
WORKDIR C:\oppai-ng
|
||||
CMD powershell -ExecutionPolicy Bypass -Command `
|
||||
Get-Location; Get-ChildItem; `
|
||||
. .\vcvarsall17.ps1 x64; .\release.ps1; `
|
||||
. .\vcvarsall17.ps1 x86; .\release.ps1
|
10
pp/oppai-ng/package
Normal file
10
pp/oppai-ng/package
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
dir=$(dirname $0)
|
||||
dir=$(readlink -f $dir)
|
||||
prevdir=$(pwd)
|
||||
cd $dir
|
||||
for d in $dir/docker/*; do
|
||||
docker run --rm -v $dir:/tmp oppai-ng:$(basename $d)
|
||||
done
|
||||
cd $prevdir
|
4
pp/oppai-ng/package.ps1
Normal file
4
pp/oppai-ng/package.ps1
Normal file
@@ -0,0 +1,4 @@
|
||||
$dir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
Push-Location $dir
|
||||
docker run --rm -v ${dir}:c:\oppai-ng oppai-ng:windows
|
||||
Pop-Location
|
36
pp/oppai-ng/release
Normal file
36
pp/oppai-ng/release
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
|
||||
dir=$(dirname $0)
|
||||
|
||||
olddir=$(pwd)
|
||||
cd $dir
|
||||
git pull origin master
|
||||
|
||||
echo -e "\nCompiling and Stripping"
|
||||
./build -static -no-pie || exit 1
|
||||
|
||||
echo -e "\nPackaging"
|
||||
folder="oppai-$(./oppai -version)-"
|
||||
folder="${folder}$(gcc -dumpmachine)"
|
||||
|
||||
mkdir -p "$folder"
|
||||
mv ./oppai $folder/oppai
|
||||
git archive HEAD --prefix=src/ -o "$folder"/src.tar
|
||||
cd "$folder"
|
||||
tar xf src.tar
|
||||
cd ..
|
||||
|
||||
rm "$folder".tar.xz
|
||||
tar -cvJf "$folder".tar.xz \
|
||||
"$folder"/oppai \
|
||||
"$folder"/src
|
||||
|
||||
echo -e "\nResult:"
|
||||
tar tf "$folder".tar.xz
|
||||
|
||||
readelf --dynamic "$folder"/oppai
|
||||
ldd "$folder"/oppai
|
||||
|
||||
rm -rf "$folder"
|
||||
cd $olddir
|
||||
|
42
pp/oppai-ng/release.ps1
Normal file
42
pp/oppai-ng/release.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
# you must allow script execution by running
|
||||
# 'Set-ExecutionPolicy RemoteSigned' in an admin powershell
|
||||
# this requires vcvars to be already set (see vcvarsall17.ps1)
|
||||
# 7zip is also required (choco install 7zip and add it to path)
|
||||
|
||||
$dir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
Push-Location "$dir"
|
||||
git pull origin master -q
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "########################" -Foreground Yellow -Background Black
|
||||
Write-Host "> Compiling and stripping" -Foreground Yellow -Background Black
|
||||
cmd /c "build.bat"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "########################" -Foreground Yellow -Background Black
|
||||
Write-Host "> Packaging" -Foreground Yellow -Background Black
|
||||
$folder = "oppai-" + $(.\oppai.exe -version) + "-windows-"
|
||||
$clout = & cl 2>&1 | %{ "$_" }
|
||||
"$clout" -match "(Microsoft.*for )([a-z0-9\-_]+)" | Out-Null
|
||||
$folder = $folder + $Matches[2]
|
||||
mkdir $folder
|
||||
Copy-Item oppai.exe $folder
|
||||
git archive HEAD --prefix=src\ -o $folder\src.zip
|
||||
Set-Location $folder
|
||||
&7z x src.zip
|
||||
Set-Location ..
|
||||
|
||||
if (Test-Path "$folder.zip") {
|
||||
Remove-Item "$folder.zip"
|
||||
}
|
||||
|
||||
&7z a "$folder.zip" $folder\oppai.exe $folder\src
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "########################" -Foreground Yellow -Background Black
|
||||
Write-Host "> Result:" -Foreground Yellow -Background Black
|
||||
&7z l "$folder.zip"
|
||||
|
||||
Remove-Item $folder -Force -Recurse
|
||||
Pop-Location
|
10
pp/oppai-ng/test/download_suite
Normal file
10
pp/oppai-ng/test/download_suite
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $(find test_suite 2>/dev/null | tail -n +2 | wc -l) = "0" ]
|
||||
then
|
||||
wget http://www.hnng.moe/stuff/test_suite_20180515.tar.xz \
|
||||
|| exit 1
|
||||
tar xf test_suite_20180515.tar.xz || exit 1
|
||||
else
|
||||
echo "using existing test_suite"
|
||||
fi
|
254
pp/oppai-ng/test/gentest.py
Normal file
254
pp/oppai-ng/test/gentest.py
Normal file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import traceback
|
||||
import argparse
|
||||
import hashlib
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
# hack to force utf-8
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
|
||||
try:
|
||||
import httplib
|
||||
except ImportError:
|
||||
import http.client as httplib
|
||||
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
import urllib.parse as urllib
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description = (
|
||||
'generates the oppai test suite. outputs c++ code to ' +
|
||||
'stdout and the json dump to a file.'
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-key',
|
||||
default = None,
|
||||
help = (
|
||||
'osu! api key. required if -input-file is not present. ' +
|
||||
'can also be specified through the OSU_API_KEY ' +
|
||||
'environment variable'
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-output-file',
|
||||
default = 'test_suite.json',
|
||||
help = 'dumps json to this file'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-input-file',
|
||||
default = None,
|
||||
help = (
|
||||
'loads test suite from this json file instead of '
|
||||
'fetching it from osu api. if set to "-", json will be '
|
||||
'read from standard input'
|
||||
)
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.key == None and 'OSU_API_KEY' in os.environ:
|
||||
args.key = os.environ['OSU_API_KEY']
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
osu_treset = time.time() + 60
|
||||
osu_ncalls = 0
|
||||
|
||||
def osu_get(conn, endpoint, paramsdict=None):
|
||||
# GETs /api/endpoint?paramsdict&k=args.key from conn
|
||||
# return json object, exits process on api errors
|
||||
global osu_treset, osu_ncalls, args
|
||||
|
||||
sys.stderr.write('%s %s\n' % (endpoint, str(paramsdict)))
|
||||
|
||||
paramsdict['k'] = args.key
|
||||
path = '/api/%s?%s' % (endpoint, urllib.urlencode(paramsdict))
|
||||
|
||||
while True:
|
||||
while True:
|
||||
if time.time() >= osu_treset:
|
||||
osu_ncalls = 0
|
||||
osu_treset = time.time() + 60
|
||||
sys.stderr.write('\napi ready\n')
|
||||
|
||||
if osu_ncalls < 60:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('waiting for api cooldown...\r')
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
try:
|
||||
conn.request('GET', path)
|
||||
osu_ncalls += 1
|
||||
r = conn.getresponse()
|
||||
|
||||
raw = ''
|
||||
|
||||
while True:
|
||||
try:
|
||||
raw += r.read()
|
||||
break
|
||||
except httplib.IncompleteRead as e:
|
||||
raw += e.partial
|
||||
|
||||
j = json.loads(raw)
|
||||
|
||||
if 'error' in j:
|
||||
sys.stderr.write('%s\n' % j['error'])
|
||||
sys.exit(1)
|
||||
|
||||
return j
|
||||
|
||||
except (httplib.HTTPException, ValueError) as e:
|
||||
sys.stderr.write('%s\n' % (traceback.format_exc()))
|
||||
|
||||
try:
|
||||
# prevents exceptions on next request if the
|
||||
# response wasn't previously read due to errors
|
||||
conn.getresponse().read()
|
||||
|
||||
except httplib.HTTPException:
|
||||
pass
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def gen_modstr(bitmask):
|
||||
# generates c++ code for a mod combination's bitmask
|
||||
mods = []
|
||||
|
||||
allmods = {
|
||||
(1<< 0, 'nf'), (1<< 1, 'ez'), (1<< 2, 'td'), (1<< 3, 'hd'),
|
||||
(1<< 4, 'hr'), (1<< 6, 'dt'), (1<< 8, 'ht'),
|
||||
(1<< 9, 'nc'), (1<<10, 'fl'), (1<<12, 'so')
|
||||
}
|
||||
|
||||
for bit, string in allmods:
|
||||
if bitmask & bit != 0:
|
||||
mods.append(string)
|
||||
|
||||
if len(mods) == 0:
|
||||
return 'nomod'
|
||||
|
||||
return ' | '.join(mods)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
if args.key == None:
|
||||
sys.stderr.write(
|
||||
'please set OSU_API_KEY or pass it as a parameter\n'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
scores = []
|
||||
|
||||
if args.input_file == None:
|
||||
# fetch a fresh test suite from osu api
|
||||
top_players = [
|
||||
124493, 4787150, 2558286, 1777162, 2831793, 50265
|
||||
]
|
||||
|
||||
osu = httplib.HTTPSConnection('osu.ppy.sh')
|
||||
|
||||
for u in top_players:
|
||||
params = { 'u': u, 'limit': 100, 'type': 'id' }
|
||||
scores += osu_get(osu, 'get_user_best', params)
|
||||
|
||||
params = { 'm': 0, 'since': '2015-11-26' }
|
||||
maps = osu_get(osu, 'get_beatmaps', params)
|
||||
|
||||
for m in maps:
|
||||
params = { 'b': m['beatmap_id'] }
|
||||
map_scores = osu_get(osu, 'get_scores', params)
|
||||
|
||||
if len(map_scores) == 0:
|
||||
sys.stderr.write('W: map has no scores???\n')
|
||||
continue
|
||||
|
||||
# note: api also returns qualified and loved, so ignore
|
||||
# maps that don't have pp in rankings
|
||||
if not 'pp' in map_scores[0]:
|
||||
sys.stderr.write('W: ignoring loved/qualified map\n')
|
||||
continue
|
||||
|
||||
for s in map_scores:
|
||||
s['beatmap_id'] = m['beatmap_id']
|
||||
|
||||
scores += map_scores
|
||||
|
||||
|
||||
with open(args.output_file, 'w+') as f:
|
||||
f.write(json.dumps(scores))
|
||||
|
||||
else:
|
||||
# load existing test suite from json file
|
||||
with open(args.input_file, 'r') as f:
|
||||
scores = json.loads(f.read())
|
||||
|
||||
|
||||
print('/* this code was automatically generated by gentest.py */')
|
||||
print('')
|
||||
|
||||
# make code a little nicer by shortening mods
|
||||
allmods = {
|
||||
'nf', 'ez', 'td', 'hd', 'hr', 'dt', 'ht', 'nc', 'fl', 'so', 'nomod'
|
||||
}
|
||||
|
||||
for mod in allmods:
|
||||
print('#define %s MODS_%s' % (mod, mod.upper()))
|
||||
|
||||
print('''
|
||||
struct score
|
||||
{
|
||||
uint32_t id;
|
||||
uint16_t max_combo;
|
||||
uint16_t n300, n100, n50, nmiss;
|
||||
uint32_t mods;
|
||||
double pp;
|
||||
};
|
||||
|
||||
struct score const suite[] =
|
||||
{''')
|
||||
|
||||
seen_hashes = []
|
||||
|
||||
for s in scores:
|
||||
# why is every value returned by osu api a string?
|
||||
line = (
|
||||
' { %s, %s, %s, %s, %s, %s, %s, %s },' %
|
||||
(
|
||||
s['beatmap_id'], s['maxcombo'], s['count300'],
|
||||
s['count100'], s['count50'], s['countmiss'],
|
||||
gen_modstr(int(s['enabled_mods'])), s['pp']
|
||||
)
|
||||
)
|
||||
|
||||
# don't include identical scores by different people
|
||||
s = hashlib.sha1(line).digest()
|
||||
if s in seen_hashes:
|
||||
continue
|
||||
|
||||
print(line)
|
||||
seen_hashes.append(s)
|
||||
|
||||
print('};\n')
|
||||
|
||||
for mod in allmods:
|
||||
print('#undef %s' % (mod))
|
||||
|
15
pp/oppai-ng/vcvarsall17.ps1
Normal file
15
pp/oppai-ng/vcvarsall17.ps1
Normal file
@@ -0,0 +1,15 @@
|
||||
# https://stackoverflow.com/questions/2124753/how-can-i-use-powershell-with-the-visual-studio-command-prompt
|
||||
|
||||
param (
|
||||
[string]$arch = "amd64"
|
||||
)
|
||||
|
||||
Push-Location "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools"
|
||||
cmd /c "VsDevCmd.bat -arch=$arch&set" |
|
||||
ForEach-Object {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("="); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])"
|
||||
}
|
||||
}
|
||||
Pop-Location
|
||||
Write-Host "`nVisual Studio 2017 Command Prompt variables set." -ForegroundColor Yellow
|
184
pp/rippoppai.py
Normal file
184
pp/rippoppai.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
oppai interface for ripple 2 / LETS
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from common.constants import gameModes
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import scoreUtils
|
||||
from constants import exceptions
|
||||
from helpers import mapsHelper
|
||||
|
||||
# constants
|
||||
MODULE_NAME = "rippoppai"
|
||||
UNIX = True if os.name == "posix" else False
|
||||
|
||||
def fixPath(command):
|
||||
"""
|
||||
Replace / with \ if running under WIN32
|
||||
|
||||
commnd -- command to fix
|
||||
return -- command with fixed paths
|
||||
"""
|
||||
if UNIX:
|
||||
return command
|
||||
return command.replace("/", "\\")
|
||||
|
||||
|
||||
class OppaiError(Exception):
|
||||
def __init__(self, error):
|
||||
self.error = error
|
||||
|
||||
class oppai:
|
||||
"""
|
||||
Oppai cacalculator
|
||||
"""
|
||||
# __slots__ = ["pp", "score", "acc", "mods", "combo", "misses", "stars", "beatmap", "map"]
|
||||
|
||||
def __init__(self, __beatmap, __score = None, acc = 0, mods = 0, tillerino = False):
|
||||
"""
|
||||
Set oppai params.
|
||||
|
||||
__beatmap -- beatmap object
|
||||
__score -- score object
|
||||
acc -- manual acc. Used in tillerino-like bot. You don't need this if you pass __score object
|
||||
mods -- manual mods. Used in tillerino-like bot. You don't need this if you pass __score object
|
||||
tillerino -- If True, self.pp will be a list with pp values for 100%, 99%, 98% and 95% acc. Optional.
|
||||
"""
|
||||
# Default values
|
||||
self.pp = None
|
||||
self.score = None
|
||||
self.acc = 0
|
||||
self.mods = 0
|
||||
self.combo = 0
|
||||
self.misses = 0
|
||||
self.stars = 0
|
||||
self.tillerino = tillerino
|
||||
|
||||
# Beatmap object
|
||||
self.beatmap = __beatmap
|
||||
|
||||
# If passed, set everything from score object
|
||||
if __score is not None:
|
||||
self.score = __score
|
||||
self.acc = self.score.accuracy * 100
|
||||
self.mods = self.score.mods
|
||||
self.combo = self.score.maxCombo
|
||||
self.misses = self.score.cMiss
|
||||
self.gameMode = self.score.gameMode
|
||||
else:
|
||||
# Otherwise, set acc and mods from params (tillerino)
|
||||
self.acc = acc
|
||||
self.mods = mods
|
||||
if self.beatmap.starsStd > 0:
|
||||
self.gameMode = gameModes.STD
|
||||
elif self.beatmap.starsTaiko > 0:
|
||||
self.gameMode = gameModes.TAIKO
|
||||
else:
|
||||
self.gameMode = None
|
||||
|
||||
# Calculate pp
|
||||
log.debug("oppai ~> Initialized oppai diffcalc")
|
||||
self.calculatePP()
|
||||
|
||||
@staticmethod
|
||||
def _runOppaiProcess(command):
|
||||
log.debug("oppai ~> running {}".format(command))
|
||||
process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
try:
|
||||
output = json.loads(process.stdout.decode("utf-8", errors="ignore"))
|
||||
if "code" not in output or "errstr" not in output:
|
||||
raise OppaiError("No code in json output")
|
||||
if output["code"] != 200:
|
||||
raise OppaiError("oppai error {}: {}".format(output["code"], output["errstr"]))
|
||||
if "pp" not in output or "stars" not in output:
|
||||
raise OppaiError("No pp/stars entry in oppai json output")
|
||||
pp = output["pp"]
|
||||
stars = output["stars"]
|
||||
|
||||
log.debug("oppai ~> full output: {}".format(output))
|
||||
log.debug("oppai ~> pp: {}, stars: {}".format(pp, stars))
|
||||
except (json.JSONDecodeError, IndexError, OppaiError) as e:
|
||||
raise OppaiError(e)
|
||||
return pp, stars
|
||||
|
||||
def calculatePP(self):
|
||||
"""
|
||||
Calculate total pp value with oppai and return it
|
||||
|
||||
return -- total pp
|
||||
"""
|
||||
# Set variables
|
||||
self.pp = None
|
||||
try:
|
||||
# Build .osu map file path
|
||||
mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID)
|
||||
log.debug("oppai ~> Map file: {}".format(mapFile))
|
||||
mapsHelper.cacheMap(mapFile, self.beatmap)
|
||||
|
||||
# Use only mods supported by oppai
|
||||
modsFixed = self.mods & 5983
|
||||
|
||||
# Check gamemode
|
||||
if self.gameMode != gameModes.STD and self.gameMode != gameModes.TAIKO:
|
||||
raise exceptions.unsupportedGameModeException()
|
||||
|
||||
command = "./pp/oppai-ng/oppai {}".format(mapFile)
|
||||
if not self.tillerino:
|
||||
# force acc only for non-tillerino calculation
|
||||
# acc is set for each subprocess if calculating tillerino-like pp sets
|
||||
if self.acc > 0:
|
||||
command += " {acc:.2f}%".format(acc=self.acc)
|
||||
if self.mods > 0:
|
||||
command += " +{mods}".format(mods=scoreUtils.readableMods(modsFixed))
|
||||
if self.combo > 0:
|
||||
command += " {combo}x".format(combo=self.combo)
|
||||
if self.misses > 0:
|
||||
command += " {misses}xm".format(misses=self.misses)
|
||||
if self.gameMode == gameModes.TAIKO:
|
||||
command += " -taiko"
|
||||
command += " -ojson"
|
||||
|
||||
# Calculate pp
|
||||
if not self.tillerino:
|
||||
# self.pp, self.stars = self._runOppaiProcess(command)
|
||||
temp_pp, self.stars = self._runOppaiProcess(command)
|
||||
if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \
|
||||
self.stars > 50:
|
||||
# Invalidate pp for bugged taiko converteds and bugged inf pp std maps
|
||||
self.pp = 0
|
||||
else:
|
||||
self.pp = temp_pp
|
||||
else:
|
||||
pp_list = []
|
||||
for acc in [100, 99, 98, 95]:
|
||||
temp_command = command
|
||||
temp_command += " {acc:.2f}%".format(acc=acc)
|
||||
pp, self.stars = self._runOppaiProcess(temp_command)
|
||||
|
||||
# If this is a broken converted, set all pp to 0 and break the loop
|
||||
if self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and pp > 800:
|
||||
pp_list = [0, 0, 0, 0]
|
||||
break
|
||||
|
||||
pp_list.append(pp)
|
||||
self.pp = pp_list
|
||||
|
||||
log.debug("oppai ~> Calculated PP: {}, stars: {}".format(self.pp, self.stars))
|
||||
except OppaiError:
|
||||
log.error("oppai ~> oppai-ng error!")
|
||||
self.pp = 0
|
||||
except exceptions.osuApiFailException:
|
||||
log.error("oppai ~> osu!api error!")
|
||||
self.pp = 0
|
||||
except exceptions.unsupportedGameModeException:
|
||||
log.error("oppai ~> Unsupported gamemode")
|
||||
self.pp = 0
|
||||
except Exception as e:
|
||||
log.error("oppai ~> Unhandled exception: {}".format(str(e)))
|
||||
self.pp = 0
|
||||
raise
|
||||
finally:
|
||||
log.debug("oppai ~> Shutting down, pp = {}".format(self.pp))
|
184
pp/rxoppai.py
Normal file
184
pp/rxoppai.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
oppai interface for ripple 2 / LETS
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from common.constants import gameModes
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import scoreUtils
|
||||
from constants import exceptions
|
||||
from helpers import mapsHelper
|
||||
|
||||
# constants
|
||||
MODULE_NAME = "rxoppai"
|
||||
UNIX = True if os.name == "posix" else False
|
||||
|
||||
def fixPath(command):
|
||||
"""
|
||||
Replace / with \ if running under WIN32
|
||||
|
||||
commnd -- command to fix
|
||||
return -- command with fixed paths
|
||||
"""
|
||||
if UNIX:
|
||||
return command
|
||||
return command.replace("/", "\\")
|
||||
|
||||
|
||||
class OppaiError(Exception):
|
||||
def __init__(self, error):
|
||||
self.error = error
|
||||
|
||||
class oppai:
|
||||
"""
|
||||
Oppai cacalculator
|
||||
"""
|
||||
# __slots__ = ["pp", "score", "acc", "mods", "combo", "misses", "stars", "beatmap", "map"]
|
||||
|
||||
def __init__(self, __beatmap, __score = None, acc = 0, mods = 0, tillerino = False):
|
||||
"""
|
||||
Set oppai params.
|
||||
|
||||
__beatmap -- beatmap object
|
||||
__score -- score object
|
||||
acc -- manual acc. Used in tillerino-like bot. You don't need this if you pass __score object
|
||||
mods -- manual mods. Used in tillerino-like bot. You don't need this if you pass __score object
|
||||
tillerino -- If True, self.pp will be a list with pp values for 100%, 99%, 98% and 95% acc. Optional.
|
||||
"""
|
||||
# Default values
|
||||
self.pp = None
|
||||
self.score = None
|
||||
self.acc = 0
|
||||
self.mods = 0
|
||||
self.combo = 0
|
||||
self.misses = 0
|
||||
self.stars = 0
|
||||
self.tillerino = tillerino
|
||||
|
||||
# Beatmap object
|
||||
self.beatmap = __beatmap
|
||||
|
||||
# If passed, set everything from score object
|
||||
if __score is not None:
|
||||
self.score = __score
|
||||
self.acc = self.score.accuracy * 100
|
||||
self.mods = self.score.mods
|
||||
self.combo = self.score.maxCombo
|
||||
self.misses = self.score.cMiss
|
||||
self.gameMode = self.score.gameMode
|
||||
else:
|
||||
# Otherwise, set acc and mods from params (tillerino)
|
||||
self.acc = acc
|
||||
self.mods = mods
|
||||
if self.beatmap.starsStd > 0:
|
||||
self.gameMode = gameModes.STD
|
||||
elif self.beatmap.starsTaiko > 0:
|
||||
self.gameMode = gameModes.TAIKO
|
||||
else:
|
||||
self.gameMode = None
|
||||
|
||||
# Calculate pp
|
||||
log.debug("rxoppai ~> Initialized oppai diffcalc")
|
||||
self.calculatePP()
|
||||
|
||||
@staticmethod
|
||||
def _runOppaiProcess(command):
|
||||
log.debug("rxoppai ~> running {}".format(command))
|
||||
process = subprocess.run((command + " -ojson"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
try:
|
||||
output = json.loads(process.stdout.decode("utf-8", errors="ignore"))
|
||||
if "code" not in output or "errstr" not in output:
|
||||
raise OppaiError("No code in json output")
|
||||
if output["code"] != 200:
|
||||
raise OppaiError("rxoppai error {}: {}".format(output["code"], output["errstr"]))
|
||||
if "pp" not in output or "stars" not in output:
|
||||
raise OppaiError("No pp/stars entry in rxoppai json output")
|
||||
pp = output["pp"]
|
||||
stars = output["stars"]
|
||||
|
||||
log.debug("rxoppai ~> full output: {}".format(output))
|
||||
log.debug("rxoppai ~> pp: {}, stars: {}".format(pp, stars))
|
||||
except (json.JSONDecodeError, IndexError, OppaiError) as e:
|
||||
raise OppaiError(e)
|
||||
return pp, stars
|
||||
|
||||
def calculatePP(self):
|
||||
"""
|
||||
Calculate total pp value with oppai and return it
|
||||
|
||||
return -- total pp
|
||||
"""
|
||||
# Set variables
|
||||
self.pp = None
|
||||
try:
|
||||
# Build .osu map file path
|
||||
mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID)
|
||||
log.debug("rxoppai ~> Map file: {}".format(mapFile))
|
||||
mapsHelper.cacheMap(mapFile, self.beatmap)
|
||||
|
||||
# Use only mods supported by oppai
|
||||
modsFixed = self.mods & 5983
|
||||
|
||||
# Check gamemode
|
||||
if self.gameMode != gameModes.STD and self.gameMode != gameModes.TAIKO:
|
||||
raise exceptions.unsupportedGameModeException()
|
||||
|
||||
command = "./pp/rxoppai/oppai {}".format(mapFile)
|
||||
if not self.tillerino:
|
||||
# force acc only for non-tillerino calculation
|
||||
# acc is set for each subprocess if calculating tillerino-like pp sets
|
||||
if self.acc > 0:
|
||||
command += " {acc:.2f}%".format(acc=self.acc)
|
||||
if self.mods > 0:
|
||||
command += " +{mods}".format(mods=scoreUtils.readableMods(modsFixed))
|
||||
if self.combo > 0:
|
||||
command += " {combo}x".format(combo=self.combo)
|
||||
if self.misses > 0:
|
||||
command += " {misses}xm".format(misses=self.misses)
|
||||
if self.gameMode == gameModes.TAIKO:
|
||||
command += " -taiko"
|
||||
command += " -ojson"
|
||||
|
||||
# Calculate pp
|
||||
if not self.tillerino:
|
||||
# self.pp, self.stars = self._runOppaiProcess(command)
|
||||
temp_pp, self.stars = self._runOppaiProcess(command)
|
||||
if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \
|
||||
self.stars > 50:
|
||||
# Invalidate pp for bugged taiko converteds and bugged inf pp std maps
|
||||
self.pp = 0
|
||||
else:
|
||||
self.pp = temp_pp
|
||||
else:
|
||||
pp_list = []
|
||||
for acc in [100, 99, 98, 95]:
|
||||
temp_command = command
|
||||
temp_command += " {acc:.2f}%".format(acc=acc)
|
||||
pp, self.stars = self._runOppaiProcess(temp_command)
|
||||
|
||||
# If this is a broken converted, set all pp to 0 and break the loop
|
||||
if self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and pp > 800:
|
||||
pp_list = [0, 0, 0, 0]
|
||||
break
|
||||
|
||||
pp_list.append(pp)
|
||||
self.pp = pp_list
|
||||
|
||||
log.debug("rxoppai ~> Calculated PP: {}, stars: {}".format(self.pp, self.stars))
|
||||
except OppaiError as e:
|
||||
log.error("rxoppai ~> rxoppai error! {}".format(str(e)))
|
||||
self.pp = 0
|
||||
except exceptions.osuApiFailException:
|
||||
log.error("rxoppai ~> osu!api error!")
|
||||
self.pp = 0
|
||||
except exceptions.unsupportedGameModeException:
|
||||
log.error("rxoppai ~> Unsupported gamemode")
|
||||
self.pp = 0
|
||||
except Exception as e:
|
||||
log.error("rxoppai ~> Unhandled exception: {}".format(str(e)))
|
||||
self.pp = 0
|
||||
raise
|
||||
finally:
|
||||
log.debug("rxoppai ~> Shutting down, pp = {}".format(self.pp))
|
31
pp/rxoppai/b3.py
Normal file
31
pp/rxoppai/b3.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
def runOppaiProcess(command):
|
||||
process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
output = json.loads(process.stdout.decode("utf-8", errors="ignore"))
|
||||
if "code" not in output or "errstr" not in output:
|
||||
print("oof")
|
||||
if output["code"] != 200:
|
||||
print("oof")
|
||||
if "pp" not in output or "stars" not in output:
|
||||
print("oof")
|
||||
pp = output["pp"]
|
||||
stars = output["stars"]
|
||||
|
||||
return pp, stars
|
||||
|
||||
command = "./oppai {}".format("../../.data/beatmaps/55.osu")
|
||||
command += " {acc:.2f}%".format(acc=99.6)
|
||||
command += " +{mods}".format(mods="HDHR")
|
||||
command += " {combo}x".format(combo=500)
|
||||
command += " {misses}xm".format(misses=1)
|
||||
command += " -ojson"
|
||||
|
||||
pp = 0.00
|
||||
stars = 0.00
|
||||
pp, stars = runOppaiProcess(command)
|
||||
print(pp)
|
BIN
pp/rxoppai/oppai
Normal file
BIN
pp/rxoppai/oppai
Normal file
Binary file not shown.
BIN
pp/rxoppai/oppai-old
Normal file
BIN
pp/rxoppai/oppai-old
Normal file
Binary file not shown.
1179
pp/rxoppai/yes.osu
Normal file
1179
pp/rxoppai/yes.osu
Normal file
File diff suppressed because it is too large
Load Diff
1179
pp/rxoppai/yes2.osu
Normal file
1179
pp/rxoppai/yes2.osu
Normal file
File diff suppressed because it is too large
Load Diff
2357
pp/rxoppai/yes3.osu
Normal file
2357
pp/rxoppai/yes3.osu
Normal file
File diff suppressed because it is too large
Load Diff
1
pp/rxoppai~05f367344c838990e809394850fb2ee794cce14e
Normal file
1
pp/rxoppai~05f367344c838990e809394850fb2ee794cce14e
Normal file
@@ -0,0 +1 @@
|
||||
C:/Users/cmyui/Desktop/codeMode/akatsuki-pp-relax
|
1
pp/rxoppai~41cc2cb18015293c90c699e1d2089aa38beec8aa
Normal file
1
pp/rxoppai~41cc2cb18015293c90c699e1d2089aa38beec8aa
Normal file
@@ -0,0 +1 @@
|
||||
C:/Users/cmyui/Desktop/codeMode/akatsuki-pp-relax
|
117
pp/wifipiano2.py
Normal file
117
pp/wifipiano2.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Wifipiano 2
|
||||
|
||||
This file has been written taking by reference code from
|
||||
osu-performance (https://github.com/ppy/osu-performance)
|
||||
by Tom94, licensed under the GNU AGPL 3 License.
|
||||
"""
|
||||
from common.constants import mods
|
||||
from common.log import logUtils as log
|
||||
from constants import exceptions
|
||||
from helpers import mapsHelper
|
||||
|
||||
|
||||
class piano:
|
||||
__slots__ = ["beatmap", "score", "pp"]
|
||||
|
||||
def __init__(self, __beatmap, __score):
|
||||
self.beatmap = __beatmap
|
||||
self.score = __score
|
||||
self.pp = 0
|
||||
self.getPP()
|
||||
|
||||
def getPP(self):
|
||||
try:
|
||||
stars = self.beatmap.starsMania
|
||||
if stars == 0:
|
||||
# This beatmap can't be converted to mania
|
||||
raise exceptions.invalidBeatmapException()
|
||||
|
||||
# Cache beatmap for cono
|
||||
mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID)
|
||||
mapsHelper.cacheMap(mapFile, self.beatmap)
|
||||
|
||||
od = self.beatmap.OD
|
||||
objects = self.score.c50+self.score.c100+self.score.c300+self.score.cKatu+self.score.cGeki+self.score.cMiss
|
||||
|
||||
score = self.score.score
|
||||
accuracy = self.score.accuracy
|
||||
scoreMods = self.score.mods
|
||||
|
||||
log.debug("[WIFIPIANO2] SCORE DATA: Stars: {stars}, OD: {od}, obj: {objects}, score: {score}, acc: {acc}, mods: {mods}".format(stars=stars, od=od, objects=objects, score=score, acc=accuracy, mods=scoreMods))
|
||||
|
||||
# ---------- STRAIN PP
|
||||
# Scale score to mods multiplier
|
||||
scoreMultiplier = 1.0
|
||||
|
||||
# Doubles score if EZ/HT
|
||||
if scoreMods & mods.EASY != 0:
|
||||
scoreMultiplier *= 0.50
|
||||
#if scoreMods & mods.HALFTIME != 0:
|
||||
# scoreMultiplier *= 0.50
|
||||
|
||||
# Calculate strain PP
|
||||
if scoreMultiplier <= 0:
|
||||
strainPP = 0
|
||||
else:
|
||||
score *= int(1.0 / scoreMultiplier)
|
||||
strainPP = pow(5.0 * max(1.0, stars / 0.0825) - 4.0, 3.0) / 110000.0
|
||||
strainPP *= 1 + 0.1 * min(1.0, float(objects) / 1500.0)
|
||||
if score <= 500000:
|
||||
strainPP *= (float(score) / 500000.0) * 0.1
|
||||
elif score <= 600000:
|
||||
strainPP *= 0.1 + float(score - 500000) / 100000.0 * 0.2
|
||||
elif score <= 700000:
|
||||
strainPP *= 0.3 + float(score - 600000) / 100000.0 * 0.35
|
||||
elif score <= 800000:
|
||||
strainPP *= 0.65 + float(score - 700000) / 100000.0 * 0.20
|
||||
elif score <= 900000:
|
||||
strainPP *= 0.85 + float(score - 800000) / 100000.0 * 0.1
|
||||
else:
|
||||
strainPP *= 0.95 + float(score - 900000) / 100000.0 * 0.05
|
||||
|
||||
# ---------- ACC PP
|
||||
# Makes sure OD is in range 0-10. If this is done elsewhere, remove this.
|
||||
scrubbedOD = min(10.0, max(0, 10.0 - od))
|
||||
|
||||
# Old formula but done backwards.
|
||||
hitWindow300 = (34 + 3 * scrubbedOD)
|
||||
|
||||
# Increases hitWindow if EZ is on
|
||||
if scoreMods & mods.EASY != 0:
|
||||
hitWindow300 *= 1.4
|
||||
|
||||
# Fiddles with DT and HT to make them match hitWindow300's ingame.
|
||||
if scoreMods & mods.DOUBLETIME != 0:
|
||||
hitWindow300 *= 1.5
|
||||
elif scoreMods & mods.HALFTIME != 0:
|
||||
hitWindow300 *= 0.75
|
||||
|
||||
# makes hit window match what it is ingame.
|
||||
hitWindow300 = int(hitWindow300) + 0.5
|
||||
if scoreMods & mods.DOUBLETIME != 0:
|
||||
hitWindow300 /= 1.5
|
||||
elif scoreMods & mods.HALFTIME != 0:
|
||||
hitWindow300 /= 0.75
|
||||
|
||||
# Calculate accuracy PP
|
||||
accPP = pow((150.0 / hitWindow300) * pow(accuracy, 16), 1.8) * 2.5
|
||||
accPP *= min(1.15, pow(float(objects) / 1500.0, 0.3))
|
||||
|
||||
# ---------- TOTAL PP
|
||||
multiplier = 1.1
|
||||
if scoreMods & mods.NOFAIL != 0:
|
||||
multiplier *= 0.90
|
||||
if scoreMods & mods.SPUNOUT != 0:
|
||||
multiplier *= 0.95
|
||||
if scoreMods & mods.EASY != 0:
|
||||
multiplier *= 0.50
|
||||
pp = pow(pow(strainPP, 1.1) + pow(accPP, 1.1), 1.0 / 1.1) * multiplier
|
||||
log.debug("[WIFIPIANO2] Calculated PP: {}".format(pp))
|
||||
|
||||
self.pp = pp
|
||||
except exceptions.invalidBeatmapException:
|
||||
log.warning("Invalid beatmap {}".format(self.beatmap.beatmapID))
|
||||
self.pp = 0
|
||||
finally:
|
||||
return self.pp
|
Reference in New Issue
Block a user