ensl.org/app/models/match.rb
Ari Timonen f6b30be278 Improve contest page and match list for maps
Improve brackets
Update gitignore and docs.
2020-03-28 22:39:41 +02:00

390 lines
14 KiB
Ruby
Executable file

# == Schema Information
#
# Table name: matches
#
# id :integer not null, primary key
# diff :integer
# forfeit :boolean
# match_time :datetime
# points1 :integer
# points2 :integer
# report :text(65535)
# score1 :integer
# score2 :integer
# created_at :datetime
# updated_at :datetime
# caster_id :string(255)
# challenge_id :integer
# contest_id :integer
# contester1_id :integer
# contester2_id :integer
# demo_id :integer
# hltv_id :integer
# map1_id :integer
# map2_id :integer
# motm_id :integer
# referee_id :integer
# server_id :integer
# week_id :integer
#
# Indexes
#
# index_matches_on_challenge_id (challenge_id)
# index_matches_on_contest_id (contest_id)
# index_matches_on_contester1_id (contester1_id)
# index_matches_on_contester2_id (contester2_id)
# index_matches_on_demo_id (demo_id)
# index_matches_on_hltv_id (hltv_id)
# index_matches_on_map1_id (map1_id)
# index_matches_on_map2_id (map2_id)
# index_matches_on_match_time (match_time)
# index_matches_on_motm_id (motm_id)
# index_matches_on_referee_id (referee_id)
# index_matches_on_score1_and_score2 (score1,score2)
# index_matches_on_server_id (server_id)
# index_matches_on_week_id (week_id)
#
class Match < ActiveRecord::Base
include Extra
MATCH_LENGTH = 7200
include Exceptions
attr_accessor :lineup, :method, :motm_name, :friendly
#attr_protected :id, :updated_at, :created_at, :diff, :points1, :points2
has_many :matchers, :dependent => :destroy
has_many :users, :through => :matchers
has_many :predictions, :dependent => :destroy
has_many :comments, -> { order("created_at") }, :as => :commentable, :dependent => :destroy
has_many :match_proposals, inverse_of: :match, dependent: :destroy
belongs_to :challenge, :optional => true
belongs_to :contest, :optional => true
belongs_to :contester1, -> { includes('team') }, :class_name => "Contester", :optional => true
belongs_to :contester2, -> { includes('team') }, :class_name => "Contester", :optional => true
belongs_to :map1, :class_name => "Map", :optional => true
belongs_to :map2, :class_name => "Map", :optional => true
belongs_to :server, :optional => true
belongs_to :referee, class_name: "User", :optional => true
belongs_to :motm, class_name: "User", :optional => true
belongs_to :demo, class_name: "DataFile", :optional => true
belongs_to :week, :optional => true
belongs_to :hltv, :class_name => "Server", :optional => true
belongs_to :stream, :class_name => "Movie", :optional => true
belongs_to :caster, :class_name => "User", :optional => true
scope :future, -> { where("match_time > UTC_TIMESTAMP()") }
scope :future5, -> { where("match_time > UTC_TIMESTAMP()").limit(5) }
scope :finished, -> { where("score1 != 0 OR score2 != 0") }
scope :realfinished, -> { where("score1 IS NOT NULL AND score2 IS NOT NULL") }
scope :unfinished, -> { where("score1 IS NULL AND score2 IS NULL") }
scope :unreffed, -> { where.not(referee_id: nil) }
scope :ordered, -> { order("match_time DESC") }
scope :chrono, -> { order("match_time ASC") }
scope :recent, -> { limit("8") }
scope :bigrecent, -> { limit("50") }
scope :active, -> { where("contest_id IN (?)", Contest.active) }
scope :on_day, -> (day) { where("match_time > ? and match_time < ?", day.beginning_of_day, day.end_of_day) }
scope :on_week, -> (time) { where("match_time > ? and match_time < ?", time.beginning_of_week, time.end_of_week) }
scope :of_contester, -> (contester) { where("contester1_id = ? OR contester2_id = ?", contester.id, contester.id) }
scope :of_user, -> (user) { includes(:matchers).where(matchers: {user_id: user.id}) }
scope :of_team, -> (team) { includes({:contester1 => :team, :contester2 => :team}).where("teams.id = ? OR teams_contesters.id = ?", team.id, team.id) }
scope :of_userteam, -> (user, team) { includes({:matchers => {:contester => :team}}).where(teams: {id: team.id}, matchers: {user_id: user.id}) }
scope :within_time, -> (from, to) { where("match_time > ? AND match_time < ?", from.utc, to.utc) }
scope :around, -> (time) { where("match_time > ? AND match_time < ?", time.ago(MATCH_LENGTH).utc, time.ago(-MATCH_LENGTH).utc) }
scope :after, -> (time) { where("match_time > ? AND match_time < ?", time.utc, time.ago(-MATCH_LENGTH).utc) }
scope :map_stats, -> { select("map1_id, COUNT(*) as num, maps.name").
joins("LEFT JOIN maps ON maps.id = map1_id").
group("map1_id").
having("map1_id is not null").
order("num DESC") }
scope :year_stats, -> { select("id, DATE_FORMAT(match_time, '%Y') as year, COUNT(*) as num").
where("match_time > '2000-01-01 01:01:01'").
group("year").
order("num DESC") }
scope :month_stats, -> { select("id, DATE_FORMAT(match_time, '%m') as month_n,
DATE_FORMAT(match_time, '%M') as month,
COUNT(*) as num").
where("match_time > '2000-01-01 01:01:01'").
group("month").
order("month_n") }
validates :contester1, :contester2, :contest, presence: true
validates :score1, :score2, format: /\A[1-9]?[0-9]\z/, allow_nil: true
validates :report, length: { maximum: 64_000 }, allow_blank: true
before_create :set_hltv
after_create :send_notifications
before_save :set_motm, if: proc { |match| match.motm_name && !match.motm_name.empty? }
before_update :reset_contest, if: proc { |match| match.score1_changed? || match.score2_changed? }
before_destroy :reset_contest
after_save :recalculate, if: proc { |match| match.score1_changed? || match.score2_changed? }
after_save :set_predictions, if: proc { |match| match.score1_changed? || match.score2_changed? }
accepts_nested_attributes_for :matchers, allow_destroy: true
def to_s
contester1.to_s + " vs " + contester2.to_s
end
def score_color
return "black" if score1.nil? || score2.nil? || contester1.nil? || contester2.nil?
return "yellow" if score1 == score2
return "green" if contester1.team == friendly && score1 > score2
return "green" if contester2.team == friendly && score2 > score1
return "red" if contester1.team == friendly && score1 < score2
"red" if contester2.team == friendly && score2 < score1
end
def preds contester
perc = Prediction.where("match_id = ? AND score#{contester} > 2", id).count()
perc != 0 ? (perc/predictions.count.to_f*100).round : 0
end
def mercs contester
matchers.where(merc: true, contester_id: contester.id)
end
def get_hltv
self.hltv = hltv ? hltv : Server.hltvs.active.unreserved_hltv_around(match_time).first
end
def demo_name
Verification.uncrap("#{contest.short_name}-#{id}_#{contester1}-vs-#{contester2}")
end
def team1_lineup
matchers.where(contester_id: contester1_id)
end
def team2_lineup
matchers.where(contester_id: contester2_id)
end
# FIXME: this is a view helper and doesn't belong here
def get_friendly(param = nil)
if param.nil?
friendly == contester1.team ? contester1 : contester2
elsif param == :score
friendly == contester1.team ? score1 : score2
elsif param == :points
friendly == contester1.team ? points1 : points2
end
end
# FIXME: this is a view helper and doesn't belong here
def get_opponent(param = nil)
if param.nil?
friendly == contester1.team ? contester2 : contester1
elsif param == :score
friendly == contester1.team ? score2 : score1
elsif param == :points
friendly == contester1.team ? points2 : points1
end
end
def get_opposing_team(team)
team == contester1.team ? contester2.team : contester1.team
end
def set_hltv
get_hltv if match_time.future?
end
def send_notifications
Profile.where("notify_any_match", 1).includes(:user).each do |p|
Notifications.match p.user, self if p.user
end
contester2.team.teamers.active.each do |teamer|
if teamer.user.profile.notify_own_match
Notifications.challenge teamer.user, self
end
end
end
def set_motm
self.motm = User.find_by_username(motm_name)
end
def set_predictions
predictions.update_all "result = 0"
predictions.update_all "result = 1", ["score1 = ? AND score2 = ?", score1, score2]
end
def after_destroy
predictions.update_all "result = 0"
contest.recalculate
end
# Since ladders are broken anyway, they are not handled here
def reset_contest
return if score1_was.nil? || score2_was.nil?
return if contest.contest_type == Contest::TYPE_LEAGUE &&
!contester2.active || !contester1.active
if score1_was == score2_was
contester1.draw = contester1.draw - 1
contester2.draw = contester2.draw - 1
elsif score1_was > score2_was
contester1.win = contester1.win - 1
contester2.loss = contester2.loss - 1
elsif score1_was < score2_was
contester1.loss = contester1.loss - 1
contester2.win = contester2.win - 1
end
unless contest.contest_type == Contest::TYPE_BRACKET
contester1.score = contester1.score - score1_was
contester2.score = contester2.score - score2_was
contester1.save!
contester2.save!
end
end
def recalculate
return if score1.nil? || score2.nil?
return if contest.contest_type == Contest::TYPE_LEAGUE &&
!contester2.active || !contester1.active
if score1 == score2
contester1.draw = contester1.draw + 1
contester2.draw = contester2.draw + 1
contester1.trend = Contester::TREND_FLAT
contester2.trend = Contester::TREND_FLAT
elsif score1 > score2
contester1.win = contester1.win + 1
contester2.loss = contester2.loss + 1
contester1.trend = Contester::TREND_UP
contester2.trend = Contester::TREND_DOWN
elsif score1 < score2
contester1.loss = contester1.loss + 1
contester2.win = contester2.win + 1
contester1.trend = Contester::TREND_DOWN
contester2.trend = Contester::TREND_UP
end
self.diff = diff ? diff : (contester2.score - contester1.score)
if contest.contest_type == Contest::TYPE_LADDER
# Dunno what all this is but its not working anyways
# self.points1 = contest.elo_score score1, score2, diff
# self.points2 = contest.elo_score score2, score1, -(diff)
# contester1.extra = contester1.extra + contest.modulus_base / 10
# contester2.extra = contester2.extra + contest.modulus_base / 10
score_diff = score2 - score1
if score_diff == 0 # Draw
if diff < 0 # contester2 has higher rank
# set contester1s rank one below contester2
contest.update_ranks(contester1, contester1.score, contester2.score - 1)
else
# set contester2s rank one below contester1
contest.update_ranks(contester2, contester2.score, contester1.score - 1)
end
elsif score_diff < 0 && diff < 0 # contester1 won and contester2 has higher rank
contest.update_ranks(contester1, contester1.score, contester2.score)
elsif score_diff > 0 && diff > 0 # contester2 won and contester1 has higher rank
contest.update_ranks(contester2, contester2.score, contester1.score)
end
elsif contest.contest_type == Contest::TYPE_LEAGUE
self.points1 = score1
self.points2 = score2
contester1.score = contester1.score + points1 < 0 ? 0 : contester1.score + points1
contester2.score = contester2.score + points2 < 0 ? 0 : contester2.score + points2
end
unless contest.contest_type == Contest::TYPE_BRACKET
contester1.save!
contester2.save!
end
end
def hltv_record(addr, pwd)
if (match_time - MATCH_LENGTH * 10) > Time.now.utc ||
(match_time + MATCH_LENGTH * 10) < Time.now.utc
raise Error, I18n.t(:hltv_request_20)
end
if hltv && hltv.recording
raise Error, I18n.t(:hltv_already) + hltv.addr
end
unless get_hltv
raise Error, I18n.t(:hltv_notavailable)
end
save!
hltv.reservation = addr
hltv.pwd = pwd
hltv.recordable = self
hltv.save!
end
def hltv_move(addr, pwd)
raise Error, I18n.t(:hltv_notset) if hltv.nil? || hltv.recording.nil?
Server.move hltv.reservation, addr, pwd
end
def hltv_stop
raise Error, I18n.t(:hltv_notset) if hltv.nil? || hltv.recording.nil?
Server.stop hltv.reservation
end
def can_create?(cuser)
cuser && cuser.admin?
end
def can_update?(cuser, params = {})
return false unless cuser
return true if cuser.admin?
if cuser.ref?
if referee == cuser
return true if Verification.contain(params,
[:score1, :score2, :forfeit, :report, :demo_id,
:motm_name, :matchers_attributes, :server_id])
return true if Verification.contain(params, [:hltv]) && !demo
end
if Verification.contain(params, [:referee_id])
return true if (params[:referee_id].to_i == cuser.id && referee_id.blank?) ||
(params[:referee_id].blank? && referee_id == cuser.id)
end
end
if contester1.team.is_leader?(cuser) || contester2.team.is_leader?(cuser)
if match_time.past?
return true if Verification.contain(params, [:score1, :score2]) &&
!score1 && !score2 && !forfeit
return true if Verification.contain(params, [:matchers_attributes])
end
return true if match_time.today? && Verification.contain(params, [:stream_id])
end
if cuser.caster? && Verification.contain(params, [:caster_id])
return true if (params[:caster_id].to_i == cuser.id && caster_id.blank?) ||
(params[:caster_id].blank? && caster_id == cuser.id)
end
false
end
def can_destroy?(cuser)
cuser && cuser.admin?
end
def can_make_proposal?(cuser)
cuser && (contester1.team.is_leader?(cuser) || contester2.team.is_leader?(cuser))
end
def user_in_match?(user)
user && (user.team == contester1.team || user.team == contester2.team)
end
def self.params(params, cuser)
# FIXME: check this
params.require(:match).permit(:diff, :forfeit, :match_time, :points1, :points2, :report, :score1, :score2, :caster_id, :challenge_id, :contest_id, :contester1_id, :contester2_id, :demo_id, :hltv_id, :map1_id, :map2_id, :motm_id, :referee_id, :server_Id, :week_id)
end
end