mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-02-16 10:02:58 -07:00
UNFINISHED - Achievements List in Android
This commit is contained in:
parent
887c68eed4
commit
f9f3b7da2d
@ -91,6 +91,12 @@
|
||||
android:theme="@style/Theme.Dolphin.Main"
|
||||
android:label="@string/cheats"/>
|
||||
|
||||
<activity
|
||||
android:name=".features.achievements.ui.AchievementsActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Dolphin.Main"
|
||||
android:label="@string/achievements"/>
|
||||
|
||||
<activity
|
||||
android:name=".activities.EmulationActivity"
|
||||
android:exported="false"
|
||||
|
||||
@ -36,6 +36,7 @@ import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogNfcFiguresManagerBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.ui.AchievementsActivity
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.model.Figure
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot
|
||||
@ -515,6 +516,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
||||
MENU_SET_IR_MODE -> setIRMode()
|
||||
MENU_ACTION_CHOOSE_DOUBLETAP -> chooseDoubleTapButton()
|
||||
MENU_ACTION_SETTINGS -> SettingsActivity.launch(this, MenuTag.SETTINGS)
|
||||
MENU_ACTION_ACHIEVEMENTS -> AchievementsActivity.launch(this)
|
||||
MENU_ACTION_SKYLANDERS -> showSkylanderPortalSettings()
|
||||
MENU_ACTION_INFINITY_BASE -> showInfinityBaseSettings()
|
||||
MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation()
|
||||
@ -1077,6 +1079,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
||||
const val MENU_ACTION_SKYLANDERS = 36
|
||||
const val MENU_ACTION_INFINITY_BASE = 37
|
||||
const val MENU_ACTION_LATCHING_CONTROLS = 38
|
||||
const val MENU_ACTION_ACHIEVEMENTS = 39
|
||||
|
||||
init {
|
||||
buttonsActionsMap.apply {
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dolphinemu.dolphinemu.features.achievements.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
class Achievement(
|
||||
var title: String = "",
|
||||
var description: String = "",
|
||||
var badgeName: String = "",
|
||||
var measuredProgress: String = "",
|
||||
var measuredPercent: Float = 0F,
|
||||
var id: Int = 0,
|
||||
var points: Int = 0,
|
||||
var unlockTime: String = "",
|
||||
var state: Int = 0,
|
||||
var category: Int = 0,
|
||||
var bucket: Int = 0,
|
||||
var unlocked: Int = 0,
|
||||
var rarity: Float = 0F,
|
||||
var rarityHardcore: Float = 0F,
|
||||
var type: Int = 0,
|
||||
var badgeUrl: String = "",
|
||||
var badgeLockedUrl: String = "")
|
||||
@ -0,0 +1,25 @@
|
||||
package org.dolphinemu.dolphinemu.features.achievements.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
class AchievementBucket(
|
||||
var numAchievements: Int) {
|
||||
var achievements: Array<Achievement> = Array(numAchievements) {Achievement()}
|
||||
var label: String = ""
|
||||
var subsetId: Int = 0
|
||||
var bucketType: Int = 0
|
||||
|
||||
companion object {
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7
|
||||
const val RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED = 8
|
||||
const val NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package org.dolphinemu.dolphinemu.features.achievements.model
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class AchievementProgressViewModel : ViewModel() {
|
||||
var buckets: ArrayList<AchievementBucket> = ArrayList()
|
||||
|
||||
fun load() {
|
||||
buckets.addAll(fetchProgress())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
external fun isGameLoaded(): Boolean
|
||||
|
||||
@JvmStatic
|
||||
external fun fetchProgress(): Array<AchievementBucket>
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import android.widget.TextView
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.ui.AchievementsActivity
|
||||
|
||||
class AchievementHeaderViewHolder(binding: ListItemHeaderBinding) : AchievementProgressItemViewHolder(binding.root) {
|
||||
private val headerName: TextView = binding.textHeaderName
|
||||
|
||||
override fun bind(activity: AchievementsActivity, item: AchievementProgressItem, position: Int) {
|
||||
headerName.setText(item.string)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAchievementProgressBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.AchievementProgressViewModel
|
||||
|
||||
class AchievementProgressAdapter(
|
||||
private val activity: AchievementsActivity,
|
||||
private val viewModel: AchievementProgressViewModel
|
||||
) : RecyclerView.Adapter<AchievementProgressItemViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AchievementProgressItemViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
AchievementProgressItem.TYPE_ACHIEVEMENT -> {
|
||||
val listItemAchievementProgressBinding = ListItemAchievementProgressBinding.inflate(inflater, parent, false)
|
||||
AchievementProgressViewHolder(listItemAchievementProgressBinding)
|
||||
}
|
||||
AchievementProgressItem.TYPE_HEADER -> {
|
||||
val listItemHeaderBinding = ListItemHeaderBinding.inflate(inflater, parent, false)
|
||||
AchievementHeaderViewHolder(listItemHeaderBinding)
|
||||
}
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AchievementProgressItemViewHolder, position: Int) {
|
||||
holder.bind(activity, getItemAt(position), position)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return viewModel.buckets.size + viewModel.buckets.sumOf { bucket -> bucket.achievements.size }
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return getItemAt(position).type
|
||||
}
|
||||
|
||||
private fun getItemAt(position: Int): AchievementProgressItem {
|
||||
var itemPosition = position
|
||||
viewModel.buckets.forEach { bucket ->
|
||||
when (itemPosition) {
|
||||
0 -> return AchievementProgressItem(bucket.label)
|
||||
in 1..bucket.achievements.size -> return AchievementProgressItem(
|
||||
bucket.achievements[itemPosition]
|
||||
)
|
||||
else -> itemPosition -= bucket.achievements.size
|
||||
}
|
||||
}
|
||||
throw IndexOutOfBoundsException()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.Achievement
|
||||
|
||||
class AchievementProgressItem {
|
||||
val achievement: Achievement?
|
||||
val string: String
|
||||
val type: Int
|
||||
|
||||
constructor(achievement: Achievement) {
|
||||
this.achievement = achievement
|
||||
string = ""
|
||||
type = TYPE_ACHIEVEMENT
|
||||
}
|
||||
|
||||
constructor(string: String) {
|
||||
achievement = null
|
||||
this.string = string
|
||||
this.type = TYPE_HEADER
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE_HEADER = 0
|
||||
const val TYPE_ACHIEVEMENT = 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dolphinemu.dolphinemu.features.achievements.ui.AchievementsActivity
|
||||
|
||||
abstract class AchievementProgressItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
abstract fun bind(activity: AchievementsActivity, item: AchievementProgressItem, position: Int)
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.FragmentAchievementsListBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.AchievementProgressViewModel
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsDividerItemDecoration
|
||||
|
||||
class AchievementProgressListFragment : Fragment() {
|
||||
private var _binding: FragmentAchievementsListBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentAchievementsListBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val activity = requireActivity() as AchievementsActivity
|
||||
val viewModel = ViewModelProvider(activity)[AchievementProgressViewModel::class.java]
|
||||
|
||||
binding.achievementsList.adapter = AchievementProgressAdapter(activity, viewModel)
|
||||
binding.achievementsList.layoutManager = LinearLayoutManager(activity)
|
||||
|
||||
val divider = SettingsDividerItemDecoration(requireActivity())
|
||||
binding.achievementsList.addItemDecoration(divider)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.achievementsList) { v: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_xtralarge)
|
||||
)
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAchievementProgressBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.AchievementProgressViewModel
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.Achievement
|
||||
import org.dolphinemu.dolphinemu.features.achievements.ui.AchievementsActivity
|
||||
|
||||
class AchievementProgressViewHolder(private val binding: ListItemAchievementProgressBinding) :
|
||||
AchievementProgressItemViewHolder(binding.getRoot()) {
|
||||
private lateinit var achievement: Achievement
|
||||
|
||||
override fun bind(activity: AchievementsActivity, item: AchievementProgressItem, position: Int) {
|
||||
achievement = item.achievement!!
|
||||
binding.achievementTitle.text = achievement.title
|
||||
binding.achievementDescription.text = achievement.description
|
||||
binding.achievementScore.text = achievement.points.toString()
|
||||
binding.achievementStatus.text = if (achievement.unlocked == 0) "Locked" else "Unlocked"
|
||||
binding.achievementProgress.text = achievement.measuredProgress
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.achievements.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.ActivityAchievementsBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.AchievementProgressViewModel
|
||||
import org.dolphinemu.dolphinemu.ui.TwoPaneOnBackPressedCallback
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||
|
||||
class AchievementsActivity : AppCompatActivity() {
|
||||
private lateinit var viewModel: AchievementProgressViewModel
|
||||
|
||||
private lateinit var binding: ActivityAchievementsBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeHelper.setTheme(this)
|
||||
enableEdgeToEdge()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
MainPresenter.skipRescanningLibrary()
|
||||
|
||||
title = getString(R.string.achievements_progress)
|
||||
|
||||
viewModel = ViewModelProvider(this)[AchievementProgressViewModel::class.java]
|
||||
viewModel.load()
|
||||
|
||||
binding = ActivityAchievementsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
onBackPressedDispatcher.addCallback(
|
||||
this,
|
||||
TwoPaneOnBackPressedCallback(binding.slidingPaneLayout)
|
||||
)
|
||||
|
||||
setSupportActionBar(binding.toolbarAchievements)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
@ColorInt val color =
|
||||
ElevationOverlayProvider(binding.toolbarAchievements.context).compositeOverlay(
|
||||
MaterialColors.getColor(binding.toolbarAchievements, R.attr.colorSurface),
|
||||
resources.getDimensionPixelSize(R.dimen.elevated_app_bar).toFloat()
|
||||
)
|
||||
binding.toolbarAchievements.setBackgroundColor(color)
|
||||
ThemeHelper.setStatusBarColor(this, color)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun launch(
|
||||
context: Context
|
||||
) {
|
||||
val intent = Intent(context, AchievementsActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ import org.dolphinemu.dolphinemu.NativeLibrary
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity
|
||||
import org.dolphinemu.dolphinemu.databinding.FragmentIngameMenuBinding
|
||||
import org.dolphinemu.dolphinemu.features.achievements.model.AchievementProgressViewModel
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
@ -115,6 +116,7 @@ class MenuFragment : Fragment(), View.OnClickListener {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val savestatesEnabled = BooleanSetting.MAIN_ENABLE_SAVESTATES.boolean
|
||||
val hasAchievements = AchievementProgressViewModel.isGameLoaded()
|
||||
val hardcoreEnabled = AchievementModel.isHardcoreModeActive()
|
||||
val savestateVisibility = if (savestatesEnabled) View.VISIBLE else View.GONE
|
||||
binding.menuQuicksave.visibility = savestateVisibility
|
||||
@ -125,6 +127,7 @@ class MenuFragment : Fragment(), View.OnClickListener {
|
||||
// will block the load and send a message to the screen.
|
||||
binding.menuQuickload.paint.isStrikeThruText = hardcoreEnabled
|
||||
binding.menuEmulationLoadRoot.paint.isStrikeThruText = hardcoreEnabled
|
||||
binding.menuAchievements.visibility = if (hasAchievements) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -186,6 +189,10 @@ class MenuFragment : Fragment(), View.OnClickListener {
|
||||
R.id.menu_emulation_load_root,
|
||||
EmulationActivity.MENU_ACTION_LOAD_ROOT
|
||||
)
|
||||
buttonsActionsMap.append(
|
||||
R.id.menu_achievements,
|
||||
EmulationActivity.MENU_ACTION_ACHIEVEMENTS
|
||||
)
|
||||
buttonsActionsMap.append(
|
||||
R.id.menu_overlay_controls,
|
||||
EmulationActivity.MENU_ACTION_OVERLAY_CONTROLS
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinator_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_achievements"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:elevation="0dp"
|
||||
app:liftOnScroll="false">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_achievements"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<androidx.slidingpanelayout.widget.SlidingPaneLayout
|
||||
android:id="@+id/sliding_pane_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/coordinator_main">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/achievement_list"
|
||||
android:name="org.dolphinemu.dolphinemu.features.achievements.ui.AchievementProgressListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout="@layout/fragment_achievements_list" />
|
||||
|
||||
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
|
||||
|
||||
<!-- We have to set the layout height at 1px because when a device forces fullscreen mode,
|
||||
inset callbacks are not triggered and using 0dp will make this view cover the entire
|
||||
display since this activity uses a constraint layout. Now, even if the callback isn't
|
||||
triggered, this view will remain at 1px height. -->
|
||||
<View
|
||||
android:id="@+id/workaround_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@android:color/transparent"
|
||||
android:clickable="true"
|
||||
android:focusable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/achievements_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -81,6 +81,12 @@
|
||||
android:text="@string/emulation_loadstate"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_achievements"
|
||||
style="@style/InGameMenuOption"
|
||||
android:text="@string/emulation_achievements"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_settings"
|
||||
style="@style/InGameMenuOption"
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/achievement_title"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="76dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Achievement" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/achievement_description"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="76dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Achievement" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/achievement_score"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="76dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Achievement" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/achievement_status"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="76dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Achievement" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/achievement_progress"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="76dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Achievement" />
|
||||
|
||||
</RelativeLayout>
|
||||
@ -604,6 +604,7 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||
<string name="emulation_screenshot">Take Screenshot</string>
|
||||
<string name="emulation_savestate">Save State</string>
|
||||
<string name="emulation_loadstate">Load State</string>
|
||||
<string name="emulation_achievements">Achievements</string>
|
||||
<string name="emulation_exit">Exit Emulation</string>
|
||||
<string name="emulation_state_slot">Slot %1$d\n\n%2$s</string>
|
||||
<string name="emulation_state_slot_empty">Slot %1$d\n\nEmpty</string>
|
||||
@ -956,6 +957,7 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||
<string name="wii_speak_permission_warning_description">Wii Speak emulation requires microphone permission. You might need to restart the game for the permission to be effective.</string>
|
||||
|
||||
<!-- Achievements -->
|
||||
<string name="achievements">Achievements</string>
|
||||
<string name="achievements_enabled">Enable Achievements</string>
|
||||
<string name="achievements_hardcore_enabled">Enable Hardcore Mode</string>
|
||||
<string name="achievements_unofficial_enabled">Enable Unofficial Achievements</string>
|
||||
@ -970,4 +972,5 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||
<string name="achievements_logout">Log Out</string>
|
||||
<string name="achievements_login_in_progress">Logging In</string>
|
||||
<string name="achievements_login_failed">Login Failed</string>
|
||||
<string name="achievements_progress">Achievements</string>
|
||||
</resources>
|
||||
|
||||
@ -55,4 +55,64 @@ Java_org_dolphinemu_dolphinemu_features_settings_model_AchievementModel_shutdown
|
||||
AchievementManager::GetInstance().Shutdown();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_achievements_model_AchievementProgressViewModel_isGameLoaded(
|
||||
JNIEnv* env, jclass)
|
||||
{
|
||||
return AchievementManager::GetInstance().IsGameLoaded();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_achievements_model_AchievementProgressViewModel_fetchProgress(
|
||||
JNIEnv *env, jclass clazz)
|
||||
{
|
||||
auto& instance = AchievementManager::GetInstance();
|
||||
if (!instance.IsGameLoaded())
|
||||
return env->NewObjectArray(0, IDCache::GetAchievementBucketClass(), nullptr);
|
||||
auto* client = instance.GetClient();
|
||||
auto* achievement_list =
|
||||
rc_client_create_achievement_list(client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL,
|
||||
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
|
||||
if (!achievement_list)
|
||||
return env->NewObjectArray(0, IDCache::GetAchievementBucketClass(), nullptr);
|
||||
|
||||
auto progress = env->NewObjectArray(achievement_list->num_buckets, IDCache::GetAchievementBucketClass(), nullptr);
|
||||
for (u32 ix = 0; ix < achievement_list->num_buckets; ix++)
|
||||
{
|
||||
auto bucket = env->NewObject(IDCache::GetAchievementBucketClass(), IDCache::GetAchievementBucketConstructor(), achievement_list->buckets[ix].num_achievements);
|
||||
env->SetObjectArrayElement(progress, ix, bucket);
|
||||
env->SetObjectField(bucket, IDCache::GetAchievementBucketLabel(), ToJString(env, achievement_list->buckets[ix].label));
|
||||
env->SetIntField(bucket, IDCache::GetAchievementBucketSubsetId(), achievement_list->buckets[ix].subset_id);
|
||||
env->SetIntField(bucket, IDCache::GetAchievementBucketBucketType(), achievement_list->buckets[ix].bucket_type);
|
||||
auto bucket_achievements = static_cast<jobjectArray>(env->GetObjectField(bucket, IDCache::GetAchievementBucketAchievements()));
|
||||
for (u32 jx = 0; jx < achievement_list->buckets[ix].num_achievements; jx++)
|
||||
{
|
||||
auto achievement = env->GetObjectArrayElement(bucket_achievements, jx);
|
||||
env->SetObjectField(achievement, IDCache::GetAchievementTitle(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->title));
|
||||
env->SetObjectField(achievement, IDCache::GetAchievementDescription(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->description));
|
||||
env->SetObjectField(achievement, IDCache::GetAchievementBadgeName(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->badge_name));
|
||||
env->SetObjectField(achievement, IDCache::GetAchievementMeasuredProgress(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->measured_progress));
|
||||
env->SetFloatField(achievement, IDCache::GetAchievementMeasuredPercent(), achievement_list->buckets[ix].achievements[jx]->measured_percent);
|
||||
env->SetIntField(achievement, IDCache::GetAchievementId(), achievement_list->buckets[ix].achievements[jx]->id);
|
||||
env->SetIntField(achievement, IDCache::GetAchievementPoints(), achievement_list->buckets[ix].achievements[jx]->points);
|
||||
// TODO: Convert to string? Or something.
|
||||
//env->SetObjectField(achievement, IDCache::GetAchievementUnlockTime(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->unlock_time));
|
||||
env->SetIntField(achievement, IDCache::GetAchievementState(), achievement_list->buckets[ix].achievements[jx]->state);
|
||||
env->SetIntField(achievement, IDCache::GetAchievementCategory(), achievement_list->buckets[ix].achievements[jx]->category);
|
||||
env->SetIntField(achievement, IDCache::GetAchievementBucket(), achievement_list->buckets[ix].achievements[jx]->bucket);
|
||||
env->SetIntField(achievement, IDCache::GetAchievementUnlocked(), achievement_list->buckets[ix].achievements[jx]->unlocked);
|
||||
env->SetFloatField(achievement, IDCache::GetAchievementRarity(), achievement_list->buckets[ix].achievements[jx]->rarity);
|
||||
env->SetFloatField(achievement, IDCache::GetAchievementRarityHardcore(), achievement_list->buckets[ix].achievements[jx]->rarity_hardcore);
|
||||
env->SetIntField(achievement, IDCache::GetAchievementType(), achievement_list->buckets[ix].achievements[jx]->type);
|
||||
env->SetObjectField(achievement, IDCache::GetAchievementBadgeUrl(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->badge_url));
|
||||
env->SetObjectField(achievement, IDCache::GetAchievementBadgeLockedUrl(), ToJString(env, achievement_list->buckets[ix].achievements[jx]->badge_locked_url));
|
||||
env->DeleteLocalRef(achievement);
|
||||
}
|
||||
env->DeleteLocalRef(bucket);
|
||||
}
|
||||
rc_client_destroy_achievement_list(achievement_list);
|
||||
return progress;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -125,6 +125,33 @@ static jclass s_audio_utils_class;
|
||||
static jmethodID s_audio_utils_get_sample_rate;
|
||||
static jmethodID s_audio_utils_get_frames_per_buffer;
|
||||
|
||||
static jclass s_achievement_bucket_class;
|
||||
static jfieldID s_achievement_bucket_label;
|
||||
static jfieldID s_achievement_bucket_subset_id;
|
||||
static jfieldID s_achievement_bucket_bucket_type;
|
||||
static jfieldID s_achievement_bucket_achievements;
|
||||
static jmethodID s_achievement_bucket_constructor;
|
||||
|
||||
static jclass s_achievement_class;
|
||||
static jfieldID s_achievement_title;
|
||||
static jfieldID s_achievement_description;
|
||||
static jfieldID s_achievement_badge_name;
|
||||
static jfieldID s_achievement_measured_progress;
|
||||
static jfieldID s_achievement_measured_percent;
|
||||
static jfieldID s_achievement_id;
|
||||
static jfieldID s_achievement_points;
|
||||
static jfieldID s_achievement_unlock_time;
|
||||
static jfieldID s_achievement_state;
|
||||
static jfieldID s_achievement_category;
|
||||
static jfieldID s_achievement_bucket;
|
||||
static jfieldID s_achievement_unlocked;
|
||||
static jfieldID s_achievement_rarity;
|
||||
static jfieldID s_achievement_rarity_hardcore;
|
||||
static jfieldID s_achievement_type;
|
||||
static jfieldID s_achievement_badge_url;
|
||||
static jfieldID s_achievement_badge_locked_url;
|
||||
static jmethodID s_achievement_constructor;
|
||||
|
||||
namespace IDCache
|
||||
{
|
||||
JNIEnv* GetEnvForThread()
|
||||
@ -575,6 +602,131 @@ jmethodID GetAudioUtilsGetFramesPerBuffer()
|
||||
return s_audio_utils_get_frames_per_buffer;
|
||||
}
|
||||
|
||||
jclass GetAchievementBucketClass()
|
||||
{
|
||||
return s_achievement_bucket_class;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBucketLabel()
|
||||
{
|
||||
return s_achievement_bucket_label;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBucketSubsetId()
|
||||
{
|
||||
return s_achievement_bucket_subset_id;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBucketBucketType()
|
||||
{
|
||||
return s_achievement_bucket_bucket_type;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBucketAchievements()
|
||||
{
|
||||
return s_achievement_bucket_achievements;
|
||||
}
|
||||
|
||||
jmethodID GetAchievementBucketConstructor()
|
||||
{
|
||||
return s_achievement_bucket_constructor;
|
||||
}
|
||||
|
||||
jclass GetAchievementClass()
|
||||
{
|
||||
return s_achievement_class;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementTitle()
|
||||
{
|
||||
return s_achievement_title;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementDescription()
|
||||
{
|
||||
return s_achievement_description;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBadgeName()
|
||||
{
|
||||
return s_achievement_badge_name;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementMeasuredProgress()
|
||||
{
|
||||
return s_achievement_measured_progress;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementMeasuredPercent()
|
||||
{
|
||||
return s_achievement_measured_percent;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementId()
|
||||
{
|
||||
return s_achievement_id;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementPoints()
|
||||
{
|
||||
return s_achievement_points;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementUnlockTime()
|
||||
{
|
||||
return s_achievement_unlock_time;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementState()
|
||||
{
|
||||
return s_achievement_state;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementCategory()
|
||||
{
|
||||
return s_achievement_category;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBucket()
|
||||
{
|
||||
return s_achievement_bucket;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementUnlocked()
|
||||
{
|
||||
return s_achievement_unlocked;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementRarity()
|
||||
{
|
||||
return s_achievement_rarity;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementRarityHardcore()
|
||||
{
|
||||
return s_achievement_rarity_hardcore;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementType()
|
||||
{
|
||||
return s_achievement_type;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBadgeUrl()
|
||||
{
|
||||
return s_achievement_badge_url;
|
||||
}
|
||||
|
||||
jfieldID GetAchievementBadgeLockedUrl()
|
||||
{
|
||||
return s_achievement_badge_locked_url;
|
||||
}
|
||||
|
||||
jmethodID GetAchievementConstructor()
|
||||
{
|
||||
return s_achievement_constructor;
|
||||
}
|
||||
|
||||
} // namespace IDCache
|
||||
|
||||
extern "C" {
|
||||
@ -816,6 +968,43 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
env->GetStaticMethodID(audio_utils_class, "getFramesPerBuffer", "()I");
|
||||
env->DeleteLocalRef(audio_utils_class);
|
||||
|
||||
const jclass achievement_bucket_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/achievements/model/AchievementBucket");
|
||||
s_achievement_bucket_class =
|
||||
reinterpret_cast<jclass>(env->NewGlobalRef(achievement_bucket_class));
|
||||
s_achievement_bucket_label = env->GetFieldID(IDCache::GetAchievementBucketClass(), "label", "Ljava/lang/String;");
|
||||
s_achievement_bucket_subset_id = env->GetFieldID(IDCache::GetAchievementBucketClass(), "subsetId", "I");
|
||||
s_achievement_bucket_bucket_type = env->GetFieldID(IDCache::GetAchievementBucketClass(), "bucketType", "I");
|
||||
s_achievement_bucket_achievements = env->GetFieldID(IDCache::GetAchievementBucketClass(), "achievements", "[Lorg/dolphinemu/dolphinemu/features/achievements/model/Achievement;");
|
||||
s_achievement_bucket_constructor =
|
||||
env->GetMethodID(achievement_bucket_class, "<init>", "(I)V");
|
||||
env->DeleteLocalRef(achievement_bucket_class);
|
||||
|
||||
const jclass achievement_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/achievements/model/Achievement");
|
||||
s_achievement_class =
|
||||
reinterpret_cast<jclass>(env->NewGlobalRef(achievement_class));
|
||||
s_achievement_title = env->GetFieldID(IDCache::GetAchievementClass(), "title", "Ljava/lang/String;");
|
||||
s_achievement_description = env->GetFieldID(IDCache::GetAchievementClass(), "description", "Ljava/lang/String;");
|
||||
s_achievement_badge_name = env->GetFieldID(IDCache::GetAchievementClass(), "badgeName", "Ljava/lang/String;");
|
||||
s_achievement_measured_progress = env->GetFieldID(IDCache::GetAchievementClass(), "measuredProgress", "Ljava/lang/String;");
|
||||
s_achievement_measured_percent = env->GetFieldID(IDCache::GetAchievementClass(), "measuredPercent", "F");
|
||||
s_achievement_id = env->GetFieldID(IDCache::GetAchievementClass(), "id", "I");
|
||||
s_achievement_points = env->GetFieldID(IDCache::GetAchievementClass(), "points", "I");
|
||||
s_achievement_unlock_time = env->GetFieldID(IDCache::GetAchievementClass(), "unlockTime", "Ljava/lang/String;");
|
||||
s_achievement_state = env->GetFieldID(IDCache::GetAchievementClass(), "state", "I");
|
||||
s_achievement_category = env->GetFieldID(IDCache::GetAchievementClass(), "category", "I");
|
||||
s_achievement_bucket = env->GetFieldID(IDCache::GetAchievementClass(), "bucket", "I");
|
||||
s_achievement_unlocked = env->GetFieldID(IDCache::GetAchievementClass(), "unlocked", "I");
|
||||
s_achievement_rarity = env->GetFieldID(IDCache::GetAchievementClass(), "rarity", "F");
|
||||
s_achievement_rarity_hardcore = env->GetFieldID(IDCache::GetAchievementClass(), "rarityHardcore", "F");
|
||||
s_achievement_type = env->GetFieldID(IDCache::GetAchievementClass(), "type", "I");
|
||||
s_achievement_badge_url = env->GetFieldID(IDCache::GetAchievementClass(), "badgeUrl", "Ljava/lang/String;");
|
||||
s_achievement_badge_locked_url = env->GetFieldID(IDCache::GetAchievementClass(), "badgeLockedUrl", "Ljava/lang/String;");
|
||||
s_achievement_constructor =
|
||||
env->GetMethodID(achievement_class, "<init>","()V");
|
||||
env->DeleteLocalRef(achievement_class);
|
||||
|
||||
return JNI_VERSION;
|
||||
}
|
||||
|
||||
@ -853,5 +1042,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
|
||||
env->DeleteGlobalRef(s_input_detector_class);
|
||||
env->DeleteGlobalRef(s_permission_handler_class);
|
||||
env->DeleteGlobalRef(s_audio_utils_class);
|
||||
env->DeleteGlobalRef(s_achievement_bucket_class);
|
||||
env->DeleteGlobalRef(s_achievement_class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,4 +124,31 @@ jclass GetAudioUtilsClass();
|
||||
jmethodID GetAudioUtilsGetSampleRate();
|
||||
jmethodID GetAudioUtilsGetFramesPerBuffer();
|
||||
|
||||
jclass GetAchievementBucketClass();
|
||||
jfieldID GetAchievementBucketLabel();
|
||||
jfieldID GetAchievementBucketSubsetId();
|
||||
jfieldID GetAchievementBucketBucketType();
|
||||
jfieldID GetAchievementBucketAchievements();
|
||||
jmethodID GetAchievementBucketConstructor();
|
||||
|
||||
jclass GetAchievementClass();
|
||||
jfieldID GetAchievementTitle();
|
||||
jfieldID GetAchievementDescription();
|
||||
jfieldID GetAchievementBadgeName();
|
||||
jfieldID GetAchievementMeasuredProgress();
|
||||
jfieldID GetAchievementMeasuredPercent();
|
||||
jfieldID GetAchievementId();
|
||||
jfieldID GetAchievementPoints();
|
||||
jfieldID GetAchievementUnlockTime();
|
||||
jfieldID GetAchievementState();
|
||||
jfieldID GetAchievementCategory();
|
||||
jfieldID GetAchievementBucket();
|
||||
jfieldID GetAchievementUnlocked();
|
||||
jfieldID GetAchievementRarity();
|
||||
jfieldID GetAchievementRarityHardcore();
|
||||
jfieldID GetAchievementType();
|
||||
jfieldID GetAchievementBadgeUrl();
|
||||
jfieldID GetAchievementBadgeLockedUrl();
|
||||
jmethodID GetAchievementConstructor();
|
||||
|
||||
} // namespace IDCache
|
||||
|
||||
Loading…
Reference in New Issue
Block a user