前言
笔者之前开发了一个CFS的俱乐部编辑器,但是由于楼主是做MOD的光做一个俱乐部编辑器是不够的,目前来说能改的还有赞助商和足协这两个方面的内容所以笔者就开发了这个足协和赞助商功能的修改器
推广一下游戏链接:https://store.steampowered.com/app/3304320/_CFS/?l=schinese
功能
- 修改赞助商的信息
- 修改足协的各项信息
预览
原理
原理主要是通过读取sqlite内表单的信息来进行修改数据,这个相信玩python的人是小case了
代码
import os
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from PIL import Image, ImageTk
from PIL.Image import Resampling
import sqlite3
class SponsorDatabaseViewer(tk.Tk):
def __init__(self):
super().__init__()
self.title("CFS杂项编辑器 BY.卡尔纳斯")
self.geometry("1000x650")
self.minsize(900, 750)
# 图标设置
self.try_set_icon("favicon.ico")
# 初始化
self.init_data()
self.init_style()
self.create_widgets()
# 绑定 Ctrl+S 快捷键到保存功能
self.bind_all('<Control-s>', self.save_data)
def try_set_icon(self, icon_path):
"""尝试设置应用程序图标"""
try:
self.iconbitmap(icon_path)
except Exception as e:
print(f"Icon not found: {e}")
def init_data(self):
"""初始化数据"""
self.sponsor_fields = [
"SponsorName", "Type", "Unlocked", "Description", "BrandOffer", "ChestOffer",
"BackOffer", "SleeveOffer", "BillboardOffer", "BibOffer", "BannerOffer",
"HeadquarterLocation", "Industry", "LocationRestriction"
]
self.fa_fields = [
"ID", "Title", "Location", "SubsidyLevel", "MainOperatorName",
"YouthOperatorName", "CompetitionOperatorName", "YouthDevelopment",
"YouthOperatorRelation", "YouthOperatorAbility", "CompetitionOperatorRelation",
"CompetitionOperatorAbility", "MainOperatorRelation", "MainOperatorAbility",
"MainOperatorFame", "YouthOperatorFame", "CompetitionOperatorFame"
]
self.field_labels = {
"SponsorName": "赞助商名称",
"Type": "类型",
"Unlocked": "是否解锁",
"Description": "描述",
"BrandOffer": "装备赞助(万)",
"ChestOffer": "胸前广告(万)",
"BackOffer": "背部广告(万)",
"SleeveOffer": "袖子广告(万)",
"BillboardOffer": "广告牌(万)",
"BibOffer": "号码布广告(万)",
"BannerOffer": "横幅广告(万)",
"HeadquarterLocation": "总部地点",
"Industry": "行业",
"LocationRestriction": "地域限制",
"ID": "ID",
"Title": "标题",
"Location": "位置",
"SubsidyLevel": "补贴级别",
"MainOperatorName": "主要运营商名称",
"YouthOperatorName": "青年运营商名称",
"CompetitionOperatorName": "竞赛运营商名称",
"YouthDevelopment": "青年发展",
"YouthOperatorRelation": "青年运营商关系",
"YouthOperatorAbility": "青年运营商能力",
"CompetitionOperatorRelation": "竞赛运营商关系",
"CompetitionOperatorAbility": "竞赛运营商能力",
"MainOperatorRelation": "主要运营商关系",
"MainOperatorAbility": "主要运营商能力",
"MainOperatorFame": "主要运营商声望",
"YouthOperatorFame": "青年运营商声望",
"CompetitionOperatorFame": "竞赛运营商声望"
}
self.sponsor_records = []
self.fa_records = []
self.displayed_records = []
self.conn = None
self.cursor = None
self.current_id = None
self.current_search = ""
self.current_mode = "Sponsor"
self.logo_dir = None
self.current_logo_name = None
self.logo_canvas = None
def init_style(self):
"""初始化样式"""
style = ttk.Style()
style.configure('TButton', font=('Arial', 10), background='#007ACC', foreground='blue', padding=5)
style.map('TButton', foreground=[('active', 'blue'), ('pressed', 'blue')])
style.configure('TLabel', font=('Arial', 10), background='#f9f9f9', foreground='#333')
style.configure('TEntry', padding=5)
style.configure('TFrame', background='#f9f9f9')
style.configure('TLabelFrame', font=('Arial', 10, 'bold'))
style.configure('TLabelframe.Label', foreground='#005C99') # 设置LabelFrame标题的颜色
def create_widgets(self):
"""创建界面元素"""
self.main_frame = ttk.Frame(self, style='TFrame')
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 控制面板
control_frame = ttk.Frame(self.main_frame, style='TFrame')
control_frame.pack(fill=tk.X, pady=5)
ttk.Button(control_frame, text="加载数据库", command=self.load_database).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="保存数据库", command=self.save_data).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="新建赞助商", command=self.create_new_sponsor).pack(side=tk.LEFT, padx=5)
# 搜索功能
self.search_var = tk.StringVar()
search_entry = ttk.Entry(control_frame, textvariable=self.search_var, width=25, style='TEntry')
search_entry.pack(side=tk.LEFT, padx=10)
search_entry.bind("<Return>", lambda event: self.search())
ttk.Button(control_frame, text="搜索", command=self.search).pack(side=tk.LEFT)
# 切换模式按钮
self.toggle_mode_btn = ttk.Button(control_frame, text="切换到足协修改", command=self.toggle_mode)
self.toggle_mode_btn.pack(side=tk.LEFT, padx=10)
# 帮助按钮
ttk.Button(control_frame, text="帮助", command=self.show_help).pack(side=tk.LEFT, padx=10)
# 主内容区
self.content_frame = ttk.Frame(self.main_frame, style='TFrame')
self.content_frame.pack(fill=tk.BOTH, expand=True)
# 赞助列表
list_frame = ttk.Frame(self.content_frame, width=200, style='TFrame')
list_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL)
self.listbox = tk.Listbox(list_frame, width=30, yscrollcommand=scrollbar.set, font=('Arial', 10))
scrollbar.config(command=self.listbox.yview)
self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.listbox.bind('<<ListboxSelect>>', self.on_select)
# 详细信息面板
self.detail_frame = ttk.Frame(self.content_frame, style='TFrame')
self.detail_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
# Canvas 组件用于显示 logo 并设置背景色
self.create_logo_canvas()
# 赞助商和足协信息字段
self.entries = {}
self.create_fields()
self.toggle_entries_visibility()
# 状态栏
self.status_var = tk.StringVar()
ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, background='#e0e0e0').pack(fill=tk.X)
def create_logo_canvas(self):
if self.logo_canvas:
self.logo_canvas.destroy()
self.logo_canvas = tk.Canvas(self.detail_frame, width=180, height=180, bg='lightgray', cursor="hand2")
self.logo_canvas.pack(pady=5, anchor="n")
self.logo_canvas.bind("<Button-1>", self.replace_logo)
def create_fields(self):
"""创建表单字段"""
for field in self.sponsor_fields + self.fa_fields:
row = ttk.Frame(self.detail_frame, style='TFrame')
row.pack(fill=tk.X, padx=5, pady=3)
ttk.Label(row, text=self.field_labels.get(field, field), width=20, style='TLabel').pack(side=tk.LEFT)
if field == "Description":
# Description 用 Text widget
text_widget = tk.Text(row, height=5, wrap=tk.WORD, font=('Arial', 10))
text_widget.pack(fill=tk.X, expand=True)
self.entries[field] = text_widget
elif field == "Type":
# Type 用 Combobox widget
combobox = ttk.Combobox(row, values=["Brand", "Generic"], state="readonly", font=('Arial', 10))
combobox.pack(fill=tk.X, expand=True)
self.entries[field] = combobox
elif field == "Unlocked":
# Unlocked 用 Combobox widget
unlocked_combobox = ttk.Combobox(row, values=["0", "1"], state="readonly", font=('Arial', 10))
unlocked_combobox.pack(fill=tk.X, expand=True)
self.entries[field] = unlocked_combobox
else:
entry = ttk.Entry(row, font=('Arial', 10))
entry.pack(fill=tk.X, expand=True)
self.entries[field] = entry
def toggle_entries_visibility(self):
"""切换字段的可见性"""
if self.current_mode == "Sponsor":
for field in self.sponsor_fields:
self.entries[field].master.pack(fill=tk.X, pady=2)
for field in self.fa_fields:
self.entries[field].master.pack_forget()
else:
for field in self.sponsor_fields:
self.entries[field].master.pack_forget()
for field in self.fa_fields:
self.entries[field].master.pack(fill=tk.X, pady=2)
def load_database(self):
"""加载数据库文件"""
path = filedialog.askopenfilename(filetypes=[("SQLite 数据库", "*.db"), ("所有文件", "*.*")])
if not path:
return
if self.conn:
self.conn.close()
self.conn = sqlite3.connect(path)
self.cursor = self.conn.cursor()
self.refresh_data()
# 确定 logo 文件夹位置
db_dir = os.path.dirname(path)
self.logo_dir = os.path.join(db_dir, "SponsorLogos")
self.status_var.set(f"已加载数据库:{path}")
messagebox.showinfo("成功", "数据库加载成功!")
def refresh_data(self):
"""刷新数据"""
if self.conn is None or self.cursor is None:
messagebox.showwarning("警告", "请先加载数据库")
return
if self.current_mode == "Sponsor":
self.refresh_sponsor_data()
else:
self.refresh_fa_data()
def refresh_sponsor_data(self):
"""刷新赞助商数据"""
query = """
SELECT SponsorName, Type, Unlocked, Description, BrandOffer, ChestOffer,
BackOffer, SleeveOffer, BillboardOffer, BibOffer, BannerOffer,
HeadquarterLocation, Industry, LocationRestriction
FROM Sponsor
"""
self.cursor.execute(query)
self.sponsor_records = self.cursor.fetchall()
self.apply_search_filter()
self.refresh_list()
def refresh_fa_data(self):
"""刷新足协数据"""
query = """
SELECT ID, Title, Location, SubsidyLevel, MainOperatorName, YouthOperatorName,
CompetitionOperatorName, YouthDevelopment, YouthOperatorRelation,
YouthOperatorAbility, CompetitionOperatorRelation,
CompetitionOperatorAbility, MainOperatorRelation, MainOperatorAbility,
MainOperatorFame, YouthOperatorFame, CompetitionOperatorFame
FROM FA
"""
self.cursor.execute(query)
self.fa_records = self.cursor.fetchall()
self.apply_search_filter()
self.refresh_list()
def on_select(self, event):
"""处理列表选择事件"""
try:
idx = self.listbox.curselection()[0]
record = self.displayed_records[idx]
self.current_id = record[0]
self.current_logo_name = record[0] if self.current_mode == "Sponsor" else record[1]
fields = self.sponsor_fields if self.current_mode == "Sponsor" else self.fa_fields
for i, field in enumerate(fields):
if field == "Description":
self.entries[field].delete(1.0, tk.END)
self.entries[field].insert(tk.END, str(record[i]))
elif field == "Type" or field == "Unlocked":
self.entries[field].set(record[i])
else:
self.entries[field].delete(0, tk.END)
self.entries[field].insert(0, str(record[i]))
# 显示 logo
self.show_logo()
except IndexError:
pass
def show_logo(self):
"""显示赞助商 Logo"""
if self.current_mode != "Sponsor" or not self.logo_dir or not os.path.exists(self.logo_dir):
self.logo_canvas.delete("all")
return
logo_path = os.path.join(self.logo_dir, f"{self.current_logo_name}.png")
if os.path.exists(logo_path):
image = Image.open(logo_path)
image = image.resize((180, 180), Resampling.LANCZOS)
self.logo_image = ImageTk.PhotoImage(image)
self.logo_canvas.create_image(90, 90, image=self.logo_image)
else:
self.logo_canvas.delete("all")
def replace_logo(self, event):
"""替换赞助商 Logo"""
if not self.current_logo_name:
messagebox.showwarning("警告", "请先选择一个赞助商")
return
file_path = filedialog.askopenfilename(
filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp"), ("所有文件", "*.*")])
if not file_path:
return
try:
image = Image.open(file_path)
# 转换为PNG格式
new_logo_path = os.path.join(self.logo_dir, f"{self.current_logo_name}.png")
image = image.resize((512, 512), Resampling.LANCZOS)
image.save(new_logo_path, "PNG")
# 更新显示的logo
self.show_logo()
messagebox.showinfo("成功", "Logo 替换成功!")
except Exception as e:
messagebox.showerror("错误", f"替换 Logo 失败:{str(e)}")
def search(self):
"""执行搜索"""
self.current_search = self.search_var.get()
self.apply_search_filter()
self.refresh_list()
def apply_search_filter(self):
"""应用当前搜索条件"""
records = self.sponsor_records if self.current_mode == "Sponsor" else self.fa_records
self.displayed_records = [record for record in records if self.current_search.lower() in ' '.join(map(str, record)).lower()]
def refresh_list(self):
"""刷新列表显示"""
self.listbox.delete(0, tk.END)
display_field = "SponsorName" if self.current_mode == "Sponsor" else "Title"
for record in self.displayed_records:
self.listbox.insert(tk.END, record[0] if display_field == "SponsorName" else record[1])
def toggle_mode(self):
"""切换模式"""
self.current_id = None
self.current_logo_name = None
self.current_mode = "FA" if self.current_mode == "Sponsor" else "Sponsor"
self.toggle_mode_btn.config(text="切换到赞助商修改" if self.current_mode == "FA" else "切换到足协修改")
if self.current_mode == "Sponsor" and not self.logo_canvas:
self.create_logo_canvas()
elif self.current_mode == "FA" and self.logo_canvas:
self.logo_canvas.destroy()
self.logo_canvas = None
self.toggle_entries_visibility()
self.refresh_data()
self.listbox.selection_clear(0, tk.END)
def save_data(self, event=None):
"""保存数据到数据库"""
if not self.conn:
messagebox.showwarning("警告", "请先加载数据库")
return
if self.current_id is None:
messagebox.showwarning("警告", f"请选择一个要编辑的 {self.current_mode}")
return
try:
# 获取当前选择的索引
current_selection_index = self.listbox.curselection()
data = {}
fields = self.sponsor_fields if self.current_mode == "Sponsor" else self.fa_fields
table = "Sponsor" if self.current_mode == "Sponsor" else "FA"
id_field = "SponsorName" if self.current_mode == "Sponsor" else "ID"
for field in fields:
if field == "Description":
data[field] = self.entries[field].get(1.0, tk.END).strip()
elif field == "Type" or field == "Unlocked":
data[field] = self.entries[field].get()
else:
data[field] = self.entries[field].get().strip()
# 更新数据库记录
set_clause = ", ".join([f"{field} = ?" for field in fields])
query = f"UPDATE {table} SET {set_clause} WHERE {id_field} = ?"
values = [data[field] for field in fields] + [self.current_id]
self.cursor.execute(query, values)
self.conn.commit()
messagebox.showinfo("成功", "数据保存成功")
# 刷新数据
self.refresh_data()
# 刷新列表并恢复到原来的选择
self.refresh_list()
if current_selection_index:
self.listbox.selection_set(current_selection_index)
self.listbox.see(current_selection_index) # 确保所选条目可见
self.on_select(None)
except Exception as e:
messagebox.showerror("错误", f"保存数据失败:{str(e)}")
def create_new_sponsor(self):
"""复制最后一个赞助商并创建一个新的条目"""
if not self.conn or not self.cursor:
messagebox.showwarning("警告", "请先加载数据库")
return
try:
# 确定下一个赞助商的名称
query = "SELECT COUNT(*) FROM Sponsor"
self.cursor.execute(query)
sponsor_count = self.cursor.fetchone()[0]
new_name = f"赞助商{sponsor_count + 1}"
# 获取最后一个赞助商记录
query = "SELECT * FROM Sponsor ORDER BY rowid DESC LIMIT 1"
self.cursor.execute(query)
last_record = self.cursor.fetchone()
if last_record:
# 假设您有14个字段,您需要确保这一点,调整到您的数据库实际字段数
new_data = list(last_record[:14])
new_data[0] = new_name # 设置新的名字
placeholder = ", ".join(["?"] * len(new_data))
columns = ", ".join(self.sponsor_fields)
insert_query = f"INSERT INTO Sponsor ({columns}) VALUES ({placeholder})"
self.cursor.execute(insert_query, new_data)
self.conn.commit()
messagebox.showinfo("成功", "新赞助商创建成功!")
self.refresh_data()
else:
messagebox.showwarning("警告", "没有可复制的记录。")
except Exception as e:
messagebox.showerror("错误", f"创建新赞助商失败:{str(e)}")
def show_help(self):
"""显示帮助信息"""
help_message = (
"类型说明:\n"
"Brand - 装备赞助商\n"
"Generic - 其他赞助商\n"
"请谨慎编辑。"
)
messagebox.showinfo("帮助", help_message)
if __name__ == "__main__":
app = SponsorDatabaseViewer()
app.mainloop()
结尾
CFS是有悟空独立开发的一款以中国足球为背景的足球游戏,由于是个人开发者目前还是处于DEMO阶段,做这个东西也是为了丰富大家的游戏体验。让更多人认识中国足球,了解中国足球也不是只为了玩烂梗。